Explore 应用市场 API
Explore 探索广场(应用市场)接口。包含两类端点:
- 公开广场(无需登录):浏览分类、公开应用列表与详情。
- 用户端申请上架(需登录):项目作者申请把已发布的应用上架到 Explore、查询申请状态、撤回申请。
可见性遵循「双门」语义:一个应用要在广场公开,既需要运营审核通过(status='approved'),又需要作者把对应 deployment 设为公开(visibility='public' 且未删除)。两道门相互独立,审批不会改写 deployment 的可见性。
源码:apps/backend/src/routes/explore.ts
认证
| 类别 | 路由前缀 | 认证 |
|---|---|---|
| 公开广场 | /api/explore | 无需登录 |
| 申请上架 | /projects | JWT Bearer Token,且需对目标项目有 write(读列表为 read)权限 |
Authorization: Bearer <JWT>申请状态机
申请(submission)在以下状态间流转:
| 状态 | 含义 |
|---|---|
pending | 已提交,待审核 |
approved | 审核通过(满足双门后即在广场展示) |
rejected | 审核驳回 |
withdrawn | 作者主动撤回 |
delisted | 已上架后被运营下架 |
pending → approved / rejected / withdrawnapproved → delistedrejected / withdrawn / delisted之后可对同一文档重新发起新申请。- 同一文档同一时刻只允许存在一个活跃申请(
pending或approved);重复申请返回409。
公开广场端点(无需登录)
GET /api/explore/categories
获取分类 key 列表。展示名由前端按 key 做国际化映射。
请求参数
无。
响应格式
{
"categories": ["agents", "marketing", "research", "finance", "lifestyle", "hr", "tools"]
}错误码
| 状态码 | 说明 |
|---|---|
500 | 获取分类失败 |
GET /api/explore/apps
获取公开应用列表。仅返回审核通过且 deployment 满足公开条件的应用(fail-closed)。
Query 参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
category | string | 否 | 按分类 key 筛选,必须是合法分类 |
featured | string | 否 | 传 true 时仅返回精选应用 |
page | int | 否 | 页码,从 1 开始,默认 1,上限 1000 |
limit / pageSize | int | 否 | 每页条数,默认 20,上限 50(limit 优先于 pageSize) |
排序固定为:精选优先(
featured降序)→ 权重降序(sort_weight)→ 审核通过时间降序(approved_at)。
响应格式
{
"items": [
{
"submission": {
"id": "uuid",
"deployment_id": "uuid",
"document_id": "uuid",
"work_type": "flow",
"category": "agents",
"title": "示例应用",
"description": "应用简介",
"featured": false,
"sort_weight": 0,
"approved_at": "2026-06-22T00:00:00.000Z"
},
"deployment": {
"id": "uuid",
"title": "示例应用",
"description": "展示用描述",
"banner_image": "https://...",
"version_major": 1,
"version_minor": 0,
"doc_type": "flow",
"team_name": "示例团队"
}
}
],
"total": 1,
"page": 1,
"pageSize": 20
}
work_type取值为flow或ui。deployment.title/description在 deployment 自身缺省时会回落到对应文档的标题/描述。
错误码
| 状态码 | code | 说明 |
|---|---|---|
400 | invalid_pagination | page / limit 不是正整数 |
400 | page_too_large | page 超过 1000 |
400 | invalid_category | category 不在合法分类列表中 |
500 | - | 获取列表失败 |
GET /api/explore/apps/:deployment_id
获取单个公开应用详情,一次性返回详情页所需字段。
路径参数
| 参数 | 说明 |
|---|---|
deployment_id | deployment ID |
响应格式
返回 { submission, deployment },结构同列表中的单个条目(见上)。
未审核通过、deployment 已转为私有或已删除的应用一律返回
404,不暴露其存在性。
错误码
| 状态码 | 说明 |
|---|---|
404 | 应用不存在或不满足公开条件 |
500 | 获取详情失败 |
用户端申请上架端点(需登录)
POST /projects/:projectId/explore-submissions
为项目下的某个 deployment 申请上架 Explore。需对项目有 write 权限。
路径参数
| 参数 | 说明 |
|---|---|
projectId | 项目 ID |
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
deployment_id | string | 是 | 待上架的 deployment ID,必须属于该项目且未删除 |
category | string | 是 | 分类 key,必须是合法分类 |
title | string | 是 | 申请标题,最长 200 字符 |
description | string | null | 否 | 申请描述,最长 5000 字符 |
{
"deployment_id": "uuid",
"category": "agents",
"title": "我的应用",
"description": "应用简介"
}响应格式(201)
{
"success": true,
"submission": {
"id": "uuid",
"deployment_id": "uuid",
"document_id": "uuid",
"project_id": "uuid",
"team_id": "uuid",
"submitted_by_user_id": "uuid",
"work_type": "flow",
"category": "agents",
"title": "我的应用",
"description": "应用简介",
"status": "pending",
"status_reason": null,
"reviewed_by_user_id": null,
"reviewed_at": null,
"featured": false,
"sort_weight": 0,
"approved_at": null,
"created_at": "2026-06-22T00:00:00.000Z",
"updated_at": "2026-06-22T00:00:00.000Z"
}
}冲突响应(409)
当该文档已有进行中(pending / approved)的申请时返回,附带现有申请,供前端渲染按钮状态:
{
"error": "该文档已有进行中的申请",
"code": "active_submission_exists",
"submission": { "id": "uuid", "status": "pending", "...": "..." }
}错误码
| 状态码 | code | 说明 |
|---|---|---|
400 | - | 请求体无效,或 deployment_id / category / title 缺失,或字段超长 |
400 | invalid_category | category 不在合法分类列表中 |
401 | - | 未登录 |
403 | - | 对该项目无 write 权限 |
404 | - | Deployment 不存在或不属于该项目 |
409 | active_submission_exists | 该文档已有进行中的申请 |
500 | - | 创建申请失败 |
GET /projects/:projectId/explore-submissions
获取本项目下的所有申请(含各状态),用于查看审核进度。需对项目有 read 权限。
路径参数
| 参数 | 说明 |
|---|---|
projectId | 项目 ID |
响应格式
{
"success": true,
"items": [
{
"id": "uuid",
"deployment_id": "uuid",
"document_id": "uuid",
"project_id": "uuid",
"team_id": "uuid",
"submitted_by_user_id": "uuid",
"work_type": "flow",
"category": "agents",
"title": "我的应用",
"description": "应用简介",
"status": "pending",
"status_reason": null,
"reviewed_by_user_id": null,
"reviewed_at": null,
"featured": false,
"sort_weight": 0,
"approved_at": null,
"created_at": "2026-06-22T00:00:00.000Z",
"updated_at": "2026-06-22T00:00:00.000Z",
"deployments": {
"id": "uuid",
"visibility": "public",
"deleted_at": null,
"version_major": 1,
"version_minor": 0
}
}
]
}每条申请附带对应 deployment 的当前可见性快照,便于前端判断「审核已过但 deployment 仍私有」等情形。
错误码
| 状态码 | 说明 |
|---|---|
401 | 未登录 |
403 | 对该项目无 read 权限 |
500 | 查询申请列表失败 |
POST /projects/:projectId/explore-submissions/:id/withdraw
撤回一个待审核申请(pending → withdrawn)。撤回不硬删,仅改状态留审计痕迹。需对项目有 write 权限。
路径参数
| 参数 | 说明 |
|---|---|
projectId | 项目 ID |
id | 申请 ID(必须属于该项目) |
请求体
无。
响应格式
{
"success": true,
"submission": { "id": "uuid", "status": "withdrawn", "...": "..." },
"idempotent": false
}对已撤回的申请重复撤回是幂等操作:返回当前状态且
idempotent: true,不报错。
错误码
| 状态码 | code | 说明 |
|---|---|---|
401 | - | 未登录 |
403 | - | 对该项目无 write 权限 |
404 | - | 申请不存在或不属于该项目 |
409 | invalid_transition | 当前状态不可撤回(如已 approved / rejected),附带当前申请 |
500 | - | 撤回申请失败 |
应用目录安装事件(需登录)
应用目录(App Catalog / 插件安装)的埋点上报,用于统计 OAuth Product 各 Release 的安装、更新与卸载。接受两种凭证:官方桌面 Host(Driver)的 OAuth 访问令牌,或网站登录会话;任何登录用户只能上报自己的事件。
POST /app-catalog/:appId/install-events
认证(二选一,按 Bearer 形态自动区分):
| 凭证 | 适用方 | 校验 |
|---|---|---|
OAuth 访问令牌(wno- 前缀) | Driver / 官方桌面 Host | 客户端必须在 OAUTH_DELEGATION_HOST_CLIENT_IDS 白名单内且为官方 standard 应用,令牌需持有 oauth:delegate scope;否则 401 invalid_token / 403 unauthorized_client / 403 insufficient_scope,不会回退到会话校验 |
| JWT Bearer Token(网站会话) | 网页前端 | 与其他登录接口一致 |
刷新令牌(wno-rt-)与授权码(wno-code-)不是访问令牌,按非会话凭证拒绝。
路径参数
| 参数 | 类型 | 说明 |
|---|---|---|
appId | string | 插件 manifest 的稳定 appId(即 OAuth Product 的 package_app_id,例如 com.example.demo),不是 Product / OAuth App 的 UUID |
请求体
{
"event_type": "install",
"release_id": "uuid",
"installation_key": "本地安装实例标识(非空字符串)"
}event_type 取 install / update / uninstall;release_id 必须是 UUID,且必须属于路径中 appId 对应的 Product;installation_key 去除首尾空白后必须非空且不超过 200 字符。可靠客户端应在请求头传递稳定的 Idempotency-Key(UTF-8 不超过 128 bytes),同一次本地动作的重试复用同一键;缺省时服务端只会为当前请求生成随机键,无法跨请求去重。
响应:新建 201 { success: true, event };同幂等键重放 200,event.idempotent 为 true。
常见错误:400 oauth_product_install_event_invalid(字段缺失或非法);400 oauth_product_install_event_release_invalid(appId 与 Release 不匹配,或 install / update 的 Release 未发布);409 oauth_product_install_event_idempotency_conflict(同一幂等键对应不同语义);403 User session token required(不是用户会话 JWT)。
应用商店包与素材下载(公开)
已发布应用的安装包与图标/截图素材的公开下载入口(Vibe Board / App Store 目录用)。两个端点都同时接受 GET 与 HEAD,支持 Range 分段下载、ETag / If-None-Match / If-Range 协商缓存,响应带 Accept-Ranges: bytes 与不可变缓存头。服务端按 SHA-256 缓存完整文件,再用固定 Content-Length 交付,不会退化为 chunked;缓存填充并发槽繁忙时返回 429 和 Retry-After: 1。
GET /public/app-store/releases/:releaseId/download
下载指定 Release 的安装包(按发布时固化的 sha256 从对象存储 CAS 填充文件缓存后回传;Content-Disposition 为附件)。
认证:无(公开)。
常见错误:400 releaseId 非 UUID;404 Release 不存在或未公开列出;416 Range 不可满足;429 app_store_download_busy 缓存填充槽位繁忙;502 存储暂时不可用。
HEAD /public/app-store/releases/:releaseId/download
同上,只返回响应头(长度、ETag、Range 支持),不传正文,用于预检下载。
GET /public/app-store/assets/:artifactId
下载发布素材(icon / 截图;仅 png / jpeg / webp,单文件 ≤10MB,Content-Disposition: inline)。
认证:无(公开)。
常见错误:400 artifactId 非 UUID;404 素材不存在;429 app_store_download_busy 缓存填充槽位繁忙;502 素材暂时不可用(含 CAS 指针非法)。
HEAD /public/app-store/assets/:artifactId
同上,只返回响应头,不传正文。