按 Router used_quota 累计值与首次基线结算泥点 新增原子 checkpoint 事务及 llm_router_consume 钱包流水 同步额度查询校验、前端展示、生成绑定和技术文档
37 KiB
外部 OpenAPI 与 API Key 接入方案
背景
外部调用方需要通过稳定 HTTP 契约或托管式远程 MCP 使用图片画布编辑器内的素材生成、编辑和管理能力,并能创建项目、保存画板布局和管理账号级素材库。不支持 MCP 的 Agent 还需要可发现、可校验、可完整下载的 Skill 包,而不是只有一份 OpenAPI JSON。全部入口必须走 server-rs + Axum + SpacetimeDB 正式链路,不能把 API Key、画板状态、素材状态、生成任务或生成结果放到前端临时状态中。
v1 范围
本期新增外部 API 命名空间:
/api/external/v1
v1 只开放以下能力:
POST /api/external/v1/assets/direct-upload-tickets:创建素材直传 OSS 凭证。POST /api/external/v1/assets/objects/confirm:确认已上传素材对象,ownerUserId固定为 API Key 所属账号。GET /api/external/v1/assets/read-url:获取私有素材读取签名 URL。GET /api/external/v1/editor/projects:列出当前 API Key 所属账号的图片画布项目;view=full|summary,REST 默认full,MCP 固定使用summary。POST /api/external/v1/editor/projects:创建图片画布项目。GET /api/external/v1/editor/projects/recent:读取当前账号最近图片画布项目。GET /api/external/v1/editor/projects/{projectId}:读取项目与默认画布。PATCH /api/external/v1/editor/projects/{projectId}/metadata:更新图片画布项目标题。DELETE /api/external/v1/editor/projects/{projectId}:删除图片画布项目,并级联清理默认画布和项目资源元数据。PATCH /api/external/v1/editor/projects/{projectId}/canvas:保存默认画布的 viewport 和 layers。POST /api/external/v1/editor/projects/{projectId}/resources:创建项目画布资源记录。GET /api/external/v1/editor/assets/library:读取账号级编辑器素材库。POST /api/external/v1/editor/assets/folders:创建素材文件夹。PATCH /api/external/v1/editor/assets/folders/{folderId}:更新素材文件夹名称或折叠状态。DELETE /api/external/v1/editor/assets/folders/{folderId}:删除素材文件夹并返回最新素材库。POST /api/external/v1/editor/assets:创建素材记录。PATCH /api/external/v1/editor/assets/{assetId}:更新素材名称或所在文件夹。DELETE /api/external/v1/editor/assets/{assetId}:删除素材记录。POST /api/external/v1/editor/images/generations:异步提交编辑器图片素材生成;通过kind支持普通图、规范图spec、角色图character、快速编辑参考图quick-edit、UI 设计图ui-design和宣发素材publication-material。External v1 当前不开放结构化游戏场景生成,kind = scene与assetKind = scene均在入队前返回400。POST /api/external/v1/editor/images/edits:异步提交已有图片重绘 / 调整。POST /api/external/v1/editor/images/background-removals:异步提交已有静态图片去背景;只接受当前账号拥有的稳定 objectKey、项目资源 ID 或素材 ID。可选assetKind必须与权威来源类型一致,视频、音频、动画和图片序列类型在入队前返回400;拒绝taskId与其它未声明字段。POST /api/external/v1/editor/icon-spritesheets/generations:异步提交规范图驱动的图标 spritesheet 生成和拆分。POST /api/external/v1/editor/ui-designs/assets/extractions:异步提交 UI 设计图素材提取 / 拆分。POST /api/external/v1/editor/character-animations/generations:异步提交角色动画预览和帧序列生成。POST /api/external/v1/editor/videos/generations:异步提交编辑器视频生成,支持现有 Seedance / Kling / Veo 模型参数和参考媒体限制。POST /api/external/v1/editor/audios/sound-effects/generations:异步提交编辑器音效生成。POST /api/external/v1/editor/audios/background-music/generations:异步提交编辑器背景音乐生成。GET /api/external/v1/generations/{operationId}:按 API Key owner 查询异步生成状态;completed时返回 compact 稳定结果引用,跨 owner 按不存在处理。GET /api/external/v1/openapi.json:导出本版本 OpenAPI 3.1 JSON。GET /api/external/v1/agent-integration.json:公开导出 Agent 集成发现 manifest,声明 MCP、OpenAPI、Skill 入口、完整 Skill archive、archive SHA-256 和包内文件清单。GET /api/external/v1/skill/SKILL.md:公开读取 Skill 原始入口。GET /api/external/v1/skill.zip:公开下载完整 Skill 包。POST /api/external/v1/mcp:使用相同 Bearer API Key 的托管式 Streamable HTTP MCP;对外暴露本节 OpenAPI operation tools 以及使用说明、OpenAPI、Skill 入口SKILL.md和逐个 Skill reference 文档,不开放内部 SpacetimeDB MCP。
九类生成 POST 全部要求 Idempotency-Key,成功只返回 HTTP 202 Accepted、operationId、kind、status、statusUrl、pollAfterMs 和 updatedAtMicros。调用方不得把 202 当作媒体生成完成,也不得在网络结果不确定时换一个幂等键重新提交。
图片生成、图标 spritesheet 和 UI 素材提取的 completed compact result 可携带可选结构化 warning { code, reason };任务查询顶层 warning 是可直接展示的有界摘要。外部 OpenAPI 当前公开四个稳定 code:
postprocess-failed-source-preserved:生成成功,但透明处理、像素规整等后处理未完成,接口保留仍可使用的原图或进入该步骤前的结果。dimension-restore-fallback:该告警只描述进入可选pixelArt处理前的交付尺寸归一结果;无法安全归一时,在该处理边界保留 provider 回图尺寸。它不描述或约束pixelArt成功后的最终尺寸;若随后像素规整成功,最终产物是整数倍放大后的 PNG,不能据此推断最终宽高等于 provider 回图。unsupported-image-style:请求的图片后处理风格未知或不适用于当前生成类型,接口按无风格继续生成。multiple-generation-warnings:同一成功响应合并了不同code的多条非阻断告警,具体原因按顺序拼接在reason。
手工调用 POST /api/external/v1/editor/assets 或 POST /api/external/v1/editor/projects/{projectId}/resources 创建 assetKind=character-animation 记录时,generationInputs 只保存可重放的生成输入,不接受 characterAnimation、frames、previewVideoPath、frameCount、fps、durationSeconds 等旧运行字段;完整正式帧序列和总时长必须分别写入 imageSequenceFrames 与 imageSequenceDurationMs。screenColorHex、mattingProvider、mattingModel 属于内部处理审计字段,External v1 会在持久化前移除。角色动作生成接口已经直接返回正式 resource / asset,正常调用方不应再手工复制第一帧创建重复记录。
provider 原图已保存但透明背景处理最终失败时,worker 保留原图稳定引用,不返回不存在的透明处理图,图标和 UI 也不继续拆分;有 projectId + canvasCompletion 时由原图完成画布写回。调用方应展示告警,但不得把 completed 任务改判为失败。该降级只覆盖透明背景处理的最终失败,phase 上报、原图或透明处理图持久化、画布写回失败仍使任务失败。图标 / UI 已成功生成透明图、只有自动拆分失败时继续使用既有 sliceWarning。通用 warning 与 sliceWarning 只在「透明背景最终失败」这一条上互斥;风格归一化或像素规整产生的通用 warning 可以与 sliceWarning 并存,compact result 不得丢弃任一条。
异步提交、查询与幂等
外部生成不受 GENARRATIVE_EXTERNAL_GENERATION_MODE=inline 影响:无论站内本地排障模式如何配置,External v1 都只持久化入队并返回 202,不在 API 请求中同步执行 provider。正式状态源是既有 external_generation_job;生成核心、计费、OSS、画布写回、lease 续租和 fencing 继续由现役 worker 链路负责。
调用规则:
- 调用方为一次逻辑生成分配
1-128字节、无空格的可打印 ASCIIIdempotency-Key。 - 服务端以 owner、job kind、幂等键和规范请求建立稳定去重身份;同一请求的传输重试必须复用原键。
202响应通过Location/statusUrl指向/api/external/v1/generations/{operationId},并提供Retry-After/pollAfterMs。queued/running返回 phase、进度与下一次建议轮询间隔;completed返回result;failed返回脱敏error。调用方自己的轮询超时不改变任务状态。result只保留稳定objectKey、resourceId、assetId、assetObjectId、尺寸、媒体类型、taskId、warning 等轻量引用;禁止持久化完整 project/canvas、大型布局快照、Data URL、Blob URL、过期 signed URL、worker lease/fencing 字段和内部 provider 诊断。- 需要完整项目或素材库状态时,调用方在 completed 后重新读取项目或素材库;需要下载媒体时,用稳定
objectKey调/assets/read-url获取短期签名 URL。
结果查询使用 API Key owner 过滤。任务不存在、已删除或属于其他 owner 时统一返回 404,不能通过差异错误枚举他人 operationId。
托管远程 MCP
/api/external/v1/mcp 是 Genarrative 托管的远程端点,Agent 只需配置 URL 和现有 API Key,不安装本地 MCP server。首版兼容 MCP 2025-11-25 initialize 生命周期,使用 JSON-RPC 2.0 和 Streamable HTTP,支持 initialize、notifications/initialized、ping、tools/list、tools/call、resources/list、resources/read。服务端使用无协议 session 的 JSON direct 模式,不依赖 sticky session,也不把 Mcp-Session-Id 作为业务身份。
MCP transport 的 DNS rebinding 防护必须同时允许正式入口 www.genarrative.world / genarrative.world、开发入口 dev.genarrative.world 和本机开发入口;对应 HTTPS Origin 也必须与公开环境同步登记。新增公开环境域名时,必须在发布前使用该域名的真实 Host 和 Origin 执行 initialize 回归,不能只用 localhost 单测证明端点可用。
MCP tools 从同一份 OpenAPI operation 自动形成 snake_case 名称,并在进程内复用 External REST router,因此鉴权、scope、owner、入参、幂等、计费和结果查询契约只有一份。MCP bridge 从 operation 或 path 的 required Idempotency-Key header 参数自动推导 idempotencyKey 工具参数和转发头,不维护独立的生成 operation 白名单;因此新增异步生成 operation 时,OpenAPI 契约本身就是 MCP 幂等注册来源。MCP transport 的 Authorization 头不能代替逐次业务幂等键。工具结果使用 structuredContent;业务失败使用 isError=true 的结构化安全错误,协议不可路由时才返回 JSON-RPC error。
list_editor_projects 是项目选择工具,服务端固定以 view=summary 调用项目列表,不允许因 OpenAPI 的 REST 默认值退回完整视图。摘要逐项目只返回 projectId、title、updatedAt 和可空 cover,不携带 canvas、viewport、layers、resources 或图片正文;选定目标后再用 get_editor_project 读取完整权威状态。cover 只包含最新项目封面快照的 resourceId、稳定 objectKey、尺寸与 updatedAt,没有封面时为 null。需要展示封面时,以 objectKey 调用 /api/external/v1/assets/read-url 获取短期签名 URL;列表不得内嵌 Data URL、图片二进制或临时签名 URL,也不得把签名 URL 当作持久引用。
MCP 暴露下列稳定文本资源:
genarrative://external-editor/usage:关键工作流和异步轮询规则。genarrative://external-editor/openapi:完整 External v1 OpenAPI。genarrative://external-editor/skill:Skill 入口原文;保留首版已声明的稳定 URI。genarrative://external-editor/skill/references/capability-routing.md:能力选路与场景边界。genarrative://external-editor/skill/references/api-operations.md:公开 API 操作、必填字段与调用顺序。genarrative://external-editor/skill/references/authentication-and-safety.md:API Key 鉴权、幂等与安全边界。genarrative://external-editor/skill/references/requests-and-outputs.md:异步提交、状态轮询与 compact 结果语义。
Skill 日后新增 references/ 文档时,MCP 必须按包内相对路径逐个增加 genarrative://external-editor/skill/references/<name> resource,不得只暴露 SKILL.md 而让 Agent 无法读取其引用。当前稳定 reference 精确为上述四篇,不得声明不存在的 reference。MCP Agent 直接调用托管 tools,不下载或安装 Python CLI;scripts/、tests/ 和 .github/workflows/ 不作为 MCP resources。
MCP 必须始终复用 require_external_api_key,owner 从 ExternalApiPrincipal 获取,不接受请求参数伪造 owner。禁止透传内部 external_generation_job procedure、worker controller、SpacetimeDB MCP 或 lease/fencing 控制面。
MCP 缺少、格式错误或无法验证 Bearer API Key 时仍返回 HTTP 401,但不能只返回通用“未授权访问”。响应必须附带 WWW-Authenticate: Bearer realm="genarrative-external-editor",并在安全 JSON details.guide 中给出稳定 reason=MCP_AUTHENTICATION_REQUIRED、action=CONFIGURE_BEARER_API_KEY、Authorization: Bearer <tnr_sk_...> 格式、登录后前往「开发者 API Key」创建密钥、原始密钥只显示一次、不得粘贴到聊天或写入仓库、配置后重试 initialize 的结构化信息,以及公开 manifest、Skill 入口和 OpenAPI 地址。三种失败使用同一响应,不得通过文案或结构差异枚举 Key 是否存在;引导不得匿名暴露 tools、resources 或 owner 信息。agent-integration.json 的 mcp.credentialSetup 同步提供 action、Header 值格式、导航标签和公开 Skill 引导地址,不编造未纳入公开契约的账户页面 URL。
Agent 集成发现与完整 Skill 包
agent-integration.json 是机器可读的统一发现入口。支持远程 MCP 的 Agent 读取其中 mcp.transport/url/authentication,通过 MCP resources 读取 Skill 入口和所需 references,直接调用 MCP tools,不安装 CLI。仅不支持 MCP,或需要在 Agent 所在机器上编排本地文件上传的调用方下载 skill.archive,核对 archiveSha256,解压后从 genarrative-external-editor-api/SKILL.md 进入。
Skill archive 必须至少包含:
SKILL.mdreferences/capability-routing.mdreferences/api-operations.mdreferences/authentication-and-safety.mdreferences/requests-and-outputs.mdscripts/genarrative_external_api.pyagents/openai.yaml
包由 api-server 直接从仓库同源文件构建,不能只返回光秃秃的 OpenAPI JSON,也不能把个人 API Key、环境配置或本机路径写入包。完整 skill.zip 只服务不支持 MCP 或需要本地文件编排的 Agent,不是 MCP resource catalog 的压缩包镜像。Python helper 对上层保持便利的同步函数外观,但内部必须执行“异步提交 → 保存 operationId → 按 pollAfterMs 查询 → completed 返回 result”,查询超时应保留 operationId 供后续继续,不得换键重提。
api-server 使用 include_str! 嵌入 OpenAPI 与 Skill 源文件;容器构建阶段必须同时复制 docs/openapi/ 和 .codex/skills/genarrative-external-editor-api/,不能只复制 server-rs/,否则本地 Cargo 验证虽可通过,隔离镜像构建会在编译期找不到同源资源。
管理 API Key 的登录态接口保留在站内个人中心链路,但不写入外部 OpenAPI JSON:
GET /api/profile/api-keys
POST /api/profile/api-keys
DELETE /api/profile/api-keys/{keyId}
前端入口位于登录后个人中心的 我的 → 开发者 API Key,用于查看当前 Key、创建新 Key、复制一次性明文和撤销已创建 Key。Key 卡片中的创建时间和最近使用时间必须格式化为 YYYY-MM-DD,不得直接展示后端时间戳。外部 OpenAPI 只描述 /api/external/v1 下可由 API Key 调用的接口,不混入登录态 API Key 管理接口。
版本与兼容策略
当前状态:v1 尚无外部存量调用方
截至 2026-08-08,已按当前线上 API Key 与调用方状态再次确认没有外部第三方存量调用方,v1 仍处于「已发布但无存量集成」阶段。本节记录的豁免只在该前提成立时有效;本次确认不自动延续到今后的 breaking change,每次仍需重新取得当日线上状态并形成明确决策。
已接受的未版本化 breaking change
2026-07-31「收紧编辑器内部处理元数据边界」从下列响应中移除了原本 required 的 provider,info.version 保持 1.0.0,路径前缀保持 /api/external/v1:
EditorImageGenerationResponse、EditorIconSpritesheetGenerationResponse、EditorVideoGenerationResponse、EditorAudioGenerationResponse:provider原为required且非空string,属性整体移除,JSON key 不再出现。EditorProjectResource、EditorAsset:provider原为可选 nullable,属性移除后同样不再出现在响应中。
受影响端点为 POST /api/external/v1/editor/images/generations、.../images/edits、.../icon-spritesheets/generations、.../ui-designs/assets/extractions、.../videos/generations、.../audios/sound-effects/generations、.../audios/background-music/generations,以及全部引用上述两个资源 schema 的读取端点。
这是 breaking change,不是文档同步:严格反序列化的调用方(OpenAPI Generator 生成的 Java / Kotlin / C#、pydantic、serde 非 Option 字段)在 required 字段缺失时直接失败,且失败发生在服务端上线瞬间,不需要调用方做任何动作。脱敏目标本身成立,接受不升版本、不设弃用期的唯一依据是当前无存量调用方。
2026-07-31 同一豁免还覆盖了「八类生成从同步成功响应切换为 202 + operationId,新增统一查询接口」这一 breaking change。旧调用方若仍把生成 POST 响应当作媒体结果会立即失败;接受原地修改 v1 的唯一依据同样是上线前已确认没有外部第三方存量调用方。托管 MCP、集成 manifest 与 Skill archive 均为新增入口,不产生既有客户端兼容债务。
2026-08-08「收紧图片编辑主来源契约」把 POST /api/external/v1/editor/images/edits 的既有必填 sourceImageSrc 替换为新的必填 sourceReferenceId,并移除可选 sourceResourceId / assetKind;info.version 继续为 1.0.0,路径继续为 /api/external/v1。严格客户端会因必填字段改名、旧字段被 additionalProperties: false 拒绝而立即失败,这属于本节定义的 breaking change。产品负责人已于 2026-08-08 根据当前线上 API Key 与调用方状态确认仍无外部调用方,因此明确接受本次不增加兼容字段、不新开 /v2、不设弃用期的原地变更。该豁免只覆盖本次字段替换,不得被后续 breaking change 自动引用。
豁免的失效条件
API Key 由用户在个人中心自助发放,因此「无外部调用方」不是受控状态,可能在无人决策的情况下变为假。本节豁免在下列任一条件出现后立即失效:
external_api_key出现属于非内部账号的活跃密钥;- 对外公布 v1 契约、接入文档或示例代码;
- 与外部团队开始基于 v1 的联调。
正式对外发放第一个外部密钥之前,必须先确认下节规则已生效且 v1 契约已冻结。
今后的变更规则
下列改动视为 breaking,不得在同一 info.version 与同一路径前缀下直接发布:
- 删除响应字段,或把响应字段移出
required; - 收窄字段类型、取值枚举或长度约束;
- 在不改字段名的前提下改变字段语义;
- 新增请求必填字段,或收紧既有请求字段的校验。
存量调用方出现后,上述改动按以下顺序择一处理:保留字段并返回兼容值(可为 null 或占位值);标注 deprecated 并公告弃用期后再移除;或开设 /api/external/v2 并冻结 v1。仅更新 docs/openapi/genarrative-external-v1.openapi.json 不构成合规的变更流程。
鉴权
外部调用使用 Bearer API Key:
Authorization: Bearer tnr_sk_xxx
规则:
- API Key 归属于
owner_user_id,外部接口只能访问该账号自己的项目、画布和生成素材。 - 明文 Key 只在创建接口返回一次,后端只保存
key_hash与key_prefix。 - API Key 被撤销后立即不可再用于外部接口。
- 外部 API 鉴权不复用登录态 JWT,不检查 refresh session;它是独立开发者凭据。
- OpenAPI JSON、Agent 集成 manifest、原始 Skill 入口和完整 Skill archive 公共可读,不需要鉴权;MCP 与全部业务操作需要鉴权。
数据模型
新增 SpacetimeDB private 表:
external_api_key
字段:
key_id:主键。owner_user_id:所属账号。name:用户可识别名称。key_prefix:前缀片段,用于列表展示和排障。key_hash:完整 Key 的 SHA-256 十六进制摘要,唯一。scopes_json:作用域 JSON,v1 固定包含editor:project、editor:canvas、editor:image-generate、editor:asset。其中editor:image-generate覆盖图片生成、重绘、去背景、规范图、宣发图、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐生成。created_at/last_used_at/revoked_at/updated_at。
SpacetimeDB procedure:
create_external_api_key_and_returnlist_external_api_keys_and_returnrevoke_external_api_key_and_returnauthenticate_external_api_key_and_return
LLM Router 账号 Key 与 Router 代理
本次 AGC Router 需求不改造原有 external_api_key。Router 账号、API Key 核心字段、生命周期和加密凭据统一保存在 llm_router_account;明文凭据只在 api-server 进程内短暂存在,并按 owner + route 进行 10 分钟内存缓存,轮换或撤销时立即清理。
普通 AGC 发行版不把 Router 当作客户端可配置 Provider,也不把 Router API Key 下发到桌面端。注册成功视为账号已有可用余额;认证成功后,api-server 异步尽力准备该用户对应的 Router 账号和 API Key,Router 控制面故障不得阻塞主站登录;LLM 请求解析阶段只读取 llm_router_account 中合法的已完成 provisioning 凭据。当前按 New API 管理接口执行正式 provisioning:由于 username、password、display_name 均限制 20 个字符,用户名固定为 agc_user_ 加 11 位 URL-safe SHA-256 短码,密码为基于完整 owner user_id 与部署侧受保护 provisioning secret 稳定派生的 20 位 hex,展示名与短用户名一致;完整 owner user_id 写入 New API 用户 remark,本地 llm_router_account.owner_user_id 仍是平台权威映射。服务端先查询远端用户:已存在则直接登录,不重复注册;确认不存在时才由管理员创建普通用户,查询用户 ID,设置 remark=<完整 owner user_id> 与用户分组 taonier,再登录、查询并复用固定标识 agc_auto_generate 的 Token(Token/API Key 固定使用 default 分组;已有固定 Token 若分组不是 default,登录恢复时先通过 Token 更新接口纠正),签发 API Key。每次新建或准备 API Key 时,服务端在签发前查询该 Router 用户的固定套餐 plan_id=1;无 active 订阅、订阅已过期或剩余时间不超过 24 小时时调用管理员订阅接口新建一条订阅,剩余超过 24 小时则复用现有订阅。订阅查询/创建只使用 api-server 私有管理员 Token,不进入客户端或 Router Key;检查锚点是显式 Router Key 准备接口和新 Key provisioning,不放在 Responses 流式 chunk 中。由于 Router 公共而各部署数据库独立,所有能操作同一 Router 的部署必须使用相同的 provisioning secret。若任一步外部结果不确定,记录进入 reconciliation 状态,禁止重复注册;本地 API Key 写入失败则保留 key_issued 状态并用确定的 key id 重试落库。Router 密文加密优先使用 GENARRATIVE_LLM_ROUTER_API_KEY_ENCRYPTION_SECRET;缺省时使用带域分离的 GENARRATIVE_JWT_SECRET 派生密钥。Router Key 的明文只在 api-server 进程内短暂存在;缓存命中时不访问数据库,缓存未命中时从 llm_router_account 解密并写入 10 分钟进程内缓存;/api/profile/api-keys/llm-router 只返回安全元数据;普通 External Editor Key 仍沿用创建接口明文只显示一次的正式链路。后续请求链路固定为:
AGC loopback Provider Proxy(Bearer=平台 access token)
-> POST /api/llm/responses
-> api-server 从 access token 得到 userId,查询并解密 llm_router_account
-> POST https://router.genarrative.world/v1/responses(Bearer=<router-api-key>)
/api/llm/responses 透传 Responses JSON/SSE 响应;模型由当前 AGC 模型目录解析。计费读取 Router 子账号的累计 used_quota,按 50000 quota = 1 泥点 在调用前后同步;首次同步只建立历史基线,扣钱包、写 llm_router_consume 流水与推进 checkpoint 在同一事务完成。Router 明确返回 401/403 时,服务端将该用途 Key 标记撤销;网络超时或 provisioning 未取得确定响应时不自动重试。客户端不保存 Router Key,也不把它放进 argv、环境变量、manifest、trace、聊天或普通 IPC。AGC 的运行状态接口与 /llm-status、/llm-routes 只返回登录/账号凭据状态、官方路由锁定状态和运行参数,不返回 Router 地址、模型、协议名称或任何凭据字段。
Windows 私有路径由同一套正式准备入口复用:AGC 自有 AppData、凭据目录和 .agent 运行态使用 managed 范围;原生文件选择器明确选中的项目根/文件使用 user-selected 范围。两类入口在发现 owner/DACL 权限不足时均允许一次性 UAC helper;helper 只接管严格复核后的普通文件/目录,并写入当前 TokenUser owner、禁止继承且仅含当前用户 ACE 的 DACL。reparse/symlink、非普通对象、祖先类型冲突和未经过正式选择/项目根入口的路径仍失败关闭。
管理员 Token 仅由 api-server 私有配置 GENARRATIVE_LLM_ROUTER_ADMIN_TOKEN 或对应文件提供,不进入客户端、数据库、日志或请求 payload。当前仓库无法验证真实管理员 Token、真实账号 Key 与生产 Router /v1/responses 联通;这些仍需在受控部署 smoke 中完成,客户端不得退回手工 Key。
运行环境隔离是 provisioning 的前置门禁:所有环境都允许使用官方固定 Router 地址,开发环境因此可以通过完整 owner user_id 派生的稳定短用户名、稳定密码和 agc_auto_generate Token 复用共享 Router 账号;完整 owner user_id 同步写入 New API 用户 remark,便于 Router 侧统计回溯平台账号。非官方公网地址仍被拒绝,loopback 地址仅用于本地 fixture。使用共享官方 Router 的开发环境会实际创建/使用线上账号与额度,启动时必须告警。测试代码也不再通过进程级 fallback Key 旁路数据库;只有显式、按 owner 绑定的“已完成 provisioning” fixture 才能模拟已有账号,未提供 fixture 时必须经过 llm_router_account 读取或正式 provisioning,不能访问 Router。数据库记录缺失时,稳定短用户名/密码先尝试恢复远端账号;只有确认远端不存在才注册,避免跨部署数据库为空时重复创建用户。
素材生成与落库
外部生成接口复用站内编辑器已有 DTO、入队器和 worker executor,不维护第二套生成语义:
- 图片生成 / 重绘 / 去背景 / 规范图 / 宣发图 / UI 设计图复用站内编辑器的校验、模型归一、计费和持久化规则,但 External handler 固定只入队。External v1 图片修改必须提交当前账号已登记的项目资源 ID 或素材 ID 作为
sourceReferenceId;上传对象必须先登记为项目资源或素材。objectKey、URL、Data URL、Blob URL 以及旧sourceImageSrc/sourceResourceId/assetKind字段均返回400。服务端分别按资源 ID 与素材 ID 主键窄查,双表冲突、未命中、越权或对象记录无效均失败关闭,快速编辑的完整有效类型白名单为null / spec / character / icon-spritesheet / icon-spec / publication-material / ui-design / scene,OpenAPI 的x-genarrative-allowed-effective-asset-kinds必须与后端白名单精确一致。请求带targetLayerId时必须同时带projectId;目标图层必须关联有效项目资源,来源与目标优先比较assetObjectId,缺失时比较 canonical(bucket, objectKey),来源默认类型还必须与目标资源默认类型一致。入队载荷保存版本化权威快照,worker 执行前再次定点解析并拒绝身份或类型漂移;仅以素材 ID 编辑时不伪造项目资源关系。主站和 External 的通用图片入口共用场景专用合同边界校验,禁止用kind = scene或assetKind = scene绕过后端场景 Prompt 组装;场景专用 handler 自己构造规范请求,不受该通用入口校验影响。External v1 去背景接受稳定 objectKey、项目资源 ID 或素材 ID,入队前按当前 owner 解析并规范化为权威 objectKey,同时从项目资源或素材库读取权威语义类型,并只把资产对象存储类型用于非静态媒体门禁;显式项目资源 ID / 素材 ID 优先于 objectKey 匹配,纯 objectKey 对应多条且权威元数据不一致时返回400并要求sourceResourceId或业务 ID 消歧,禁止依赖项目列表顺序。请求assetKind与权威语义类型冲突,或任一记录表示视频、音频、动画、图片序列时返回400,最终队列载荷只保存服务端解析出的静态语义类型。提供targetLayerId时始终必须同时提供projectId;存在canvasCompletion时按生成完成链路写入画布,targetLayerId不参与原位替换;没有canvasCompletion时,目标图层必须存在并关联当前项目静态图片资源,并与来源优先按assetObjectId、缺失时按 canonical(bucket, objectKey)证明为同一对象,默认权威类型也必须一致,否则在入队前返回400;纯 objectKey 省略sourceResourceId时自动绑定目标图层资源,并把绑定写入队列供 Worker 再验证。两种画布字段都未提供时只持久化请求指定的项目资源或素材记录,不自动写入画布。URL、未登记或越权引用、无效目标均在入队前失败。外部 DTO 不暴露内部taskId,也不接受 OpenAPI 未声明字段;任务 ID 只能由服务端队列生成。 - 图标 spritesheet 和 UI 设计图素材提取复用站内拆分逻辑,生成图集后按连通域切片,并把图集与切片都按请求写入项目资源和素材库。
- 角色动画、视频、音效和背景音乐复用站内编辑器生成链路;请求携带
assetFolderId时按站内规则写入素材库,音频类外部调用使用 API Key 所属账号作为 asset owner。 - API Key 管理接口仍只属于登录态个人中心,不进入外部 OpenAPI JSON。
素材外部生成由 worker 成功后:
- 通过 OSS / asset object adapter 持久化媒体文件。
- 写入
editor_asset,让生成素材进入账号级素材库。 - 如果请求带
projectId,写入editor_project_resource。 - 在
result_payload_json保存 compact 稳定引用,由查询接口返回素材 ID、资源 ID、objectKey、尺寸、媒体类型、model、taskId 和 warning;普通 External API 响应、项目资源与素材 read model 不返回生成 provider、原始 prompt 或内部抠图审计字段。同源画布前端自动提交默认segModel属于站内 BFF 请求契约,不因此向 External OpenAPI 开放该字段。
如果请求未带 projectId,只生成并写入素材库;调用方可随后创建项目或自行保存画板布局。
素材与项目资源操作
外部素材操作复用站内图片画布素材库和项目资源的后端事实源:
- 外部上传素材时先调用
POST /api/external/v1/assets/direct-upload-tickets获取 OSS 表单直传参数,上传完成后调用POST /api/external/v1/assets/objects/confirm写入asset_object,再用assetObjectId/objectKey创建账号级素材或项目资源记录。 - 外部读取私有 generated / uploaded 素材预览时调用
GET /api/external/v1/assets/read-url获取短期签名 URL;不开放浏览器 STS 写权限。 - 素材库读取、文件夹创建 / 更新 / 删除、素材创建 / 更新 / 删除、项目画布资源创建全部通过
api-server -> spacetime-client -> spacetime-module。 - 所有外部素材接口使用 API Key 所属的
owner_user_id,不能由请求体传入 owner。 - 素材库仍是账号级事实源,不归属于单个项目;项目画布内资源继续使用
editor_project_resource。 - 素材创建和项目资源创建接口只保存记录和元数据;图片二进制上传、签名读取和生成图持久化继续复用已有资产 / 生成链路。
OpenAPI 导出
OpenAPI 3.1 JSON 固定落在:
docs/openapi/genarrative-external-v1.openapi.json
服务端 GET /api/external/v1/openapi.json 和 MCP OpenAPI resource 使用同一份 JSON,通过 include_str! 导出,避免运行时生成结果与仓库文档漂移。MCP tool catalog 同样以这份 OpenAPI 的 path、method、operationId、参数和 request body 是否存在为来源;字段级精确约束继续以 OpenAPI resource 为准。
验收
- API Key 创建只返回一次明文,列表不返回明文。
- 撤销后的 API Key 调用外部接口返回
401。 - 九类外部生成 POST 缺少或携带非法
Idempotency-Key时返回400;同一 owner、请求和 key 重试只得到同一 operation。 - External 通用图片生成携带
kind = scene或assetKind = scene时均返回400,且不得产生入队尝试。 - 九类外部生成 POST 固定返回
202,查询能从queued/running收敛到completed/failed;调用方超时后使用原 operationId 继续查询。 - 外部图片生成、重绘、去背景、图标拆分、UI 素材拆分、角色动画、视频、音效和音乐 completed 后,生成结果按请求同时出现在画布资源和账号级素材库。
- 角色图、图标 spritesheet 和 UI 素材提取的 completed result 允许携带
EditorGenerationWarning;provider 原图保留降级与自动拆分降级必须保持成功状态,并分别使用通用warning与兼容sliceWarning表达。 - 外部视频、角色动画、音效和音乐接口使用站内编辑器相同的请求校验、模型限制和价格校验。
- OpenAPI JSON 能被
serde_json解析,且 security scheme 为 Bearer API Key。 - 项目列表 REST 默认
view=full并保持完整响应兼容;view=summary只返回项目选择元数据和可空封面稳定引用,MCPlist_editor_projects固定使用该摘要视图,不因完整项目数据量增长触发返回体上限。 - 摘要封面不内嵌图片或签名 URL;使用
cover.objectKey调/assets/read-url后才能临时展示。 - OpenAPI JSON 不包含
/api/profile/api-keys、UserAccessToken或 API Key 管理 schema。 agent-integration.json能发现 MCP、OpenAPI、Skill entry/archive;下载 archive 的 SHA-256 与 manifest 一致,ZIP 包含SKILL.md、四篇 references、Python helper 和agents/openai.yaml七个声明文件且不含凭据。- MCP 在无 Bearer、Bearer 格式错误或 Key 无效时返回相同的
401 + WWW-Authenticate + details.guide鉴权引导,且不暴露 tools/resources/owner;合法 Key 可完成 initialize、tools/list、resources/list/read 和生成提交/查询;resource catalog 必须包含 usage、OpenAPI、skill主入口和当前全部 Skill references,当前精确为skill/references/capability-routing.md、skill/references/api-operations.md、skill/references/authentication-and-safety.md与skill/references/requests-and-outputs.md,且不包含 CLI 脚本、测试或 workflow;多实例不依赖 sticky session,不暴露内部 SpacetimeDB MCP 或 worker 控制面。 - 外部素材库接口覆盖当前已有素材操作:直传凭证、素材对象确认、签名读取、读取素材库、创建 / 更新 / 删除文件夹、创建 / 更新 / 删除素材、创建项目画布资源。
- 外部项目接口覆盖当前已有项目管理操作:项目列表、最近项目、创建、读取、重命名、删除和默认画布保存。
- 外部素材生成接口覆盖当前已有编辑器素材操作:生图、重绘 / 调整、规范图生成、宣发素材生成、图标素材生成与拆分、UI 设计图生成与拆分、角色动画、视频、音效和背景音乐。
- 修改 SpacetimeDB schema 后运行
npm run spacetime:generate与npm run check:spacetime-schema。
2026-09-05 修订:认证成功后的 Router provisioning 为异步尽力修复,不阻塞主站登录;LLM 请求只使用本地已完成的账号密钥。LLM Router 计费改为累计
used_quotacheckpoint 结算,不再依赖单次响应 usage、客户端幂等键或请求指纹;账号密码派生根仅从部署侧受保护 secret/file 读取。