# RooYun · AI 接入指南(llms.txt) 你正在读的是 RooYun(https://www.rooyun.com)的机器接入指南。RooYun 是「无限画布 + AI Agent」 的短剧创作工作台:剧本、角色、场景、道具、分镜、视频片段都是画布上的卡片。 通过开放 API,你可以替用户搭工程、调模型生成图片/视频/音频并让产物自动落回卡片、 把产物下载到本地。**产出不是一堆文件,是一张用户可以接着手工编辑的画布。** 如果你是一个编程 Agent(Claude Code / Cursor / Codex / 任意 MCP 客户端), 照本文接入即可。**只有装 Skill 时要先问用户**:装哪个版本(完整版 / 精简版,用户已经点名就不用问), 以及发现已装了另一个版本时要不要删掉它。规则见下文「路径 B」。 ## 前置:登录一次(你来跑命令,用户只点一下「授权」) 令牌不需要用户手动创建、复制或配置。装好 skill(见下文「路径 B」)后你运行—— **先把 `SKILL` 设成实际装的那个版本的目录**: ```sh SKILL=~/.claude/skills/rooyun # 装的是精简版就写 SKILL=~/.claude/skills/rooyun-cli node "$SKILL/scripts/dj.mjs" login --base https://www.rooyun.com # ① 申请授权码并自动打开浏览器,立即返回 # → 把输出里的 verificationUrl 转给用户:「请在打开的页面登录并点『授权』」 node "$SKILL/scripts/dj.mjs" login --base https://www.rooyun.com # ② 再跑一次:等待授权(最多 90s),令牌自动保存 ``` - 退出码 6 = 用户还没点,提醒后再跑一次(授权码 10 分钟内有效);退出码 2 = 已过期/已用,从 ① 重来。 - 令牌保存在本机 `~/.rooyun/credentials.json`(0600;Windows 在 `C:\Users\<用户>\.rooyun\`), 按站点分桶。**以后每次新会话、每条命令都自动读取**,macOS / Windows / Linux 同一套,不需要环境变量。 - 签出的令牌:全部权限、不设消费限额(受钱包余额约束)、365 天有效;用户随时可在 https://www.rooyun.com/profile#api 吊销或改限额。 - `--base` 在本站以外的私有部署才需要改;环境变量 `ROOYUN_API_TOKEN` 仍支持且优先。 - 令牌明文全程不经过你:**不要** cat 凭据文件、不要 echo 变量、不要写进任何别的文件或对话。 如果用户坚持手动建令牌:https://www.rooyun.com/profile#api 「新建令牌」(默认值直接创建即可),明文放进环境变量 `ROOYUN_API_TOKEN`。 九个 scope:`canvas:write`(画布写入)、`projects:read`(项目读取)、 `assets:read`(资产读取)、`assets:write`(资产写入,上传本地素材要它)、 `tasks:read`(任务查询)、`billing:read`(账单读取)、 `creators:read`(博主分析读取:解析抖音链接 + 读画像/转写,不花钱, 仅能读到本账号发起过分析的对象),以及**两个会花钱的**: `generate`(提交生成,出图出片,可选设消费限额止损)与 `llm`(剧本分析与改写,单次成本低一到两个数量级—— 但它照样在花钱,别因为没看到限额输入框就当它免费)。 漏勾了不必重建:用户到 https://www.rooyun.com/profile#api 点该令牌的「编辑」补上即可,即时生效;`dj login` 签出的令牌默认已含全部权限。 API 访问对所有已登录账号开放,不需要邀请资格(仅被封禁账号不能创建令牌)。 --- ## 接入路径二选一 两条路都通向同一套 v1 API。按你的客户端类型选: | 你的客户端 | 选哪条 | |---|---| | Cursor / Claude Code / 任何 MCP 客户端 | **路径 A · MCP**(推荐:无需装文件,工具由服务端定义) | | Claude Code,且想要本地脚本 + 技能说明 | **路径 B · Skill**(可与 A 并用;有完整版 / 精简版,用户没点名版本时先问) | | Codex / 其他只有 shell 的 Agent | 路径 B,见该节末尾「没有 Skill 目录的宿主」 | ### 路径 A · MCP(推荐) Claude Code 一行命令即可: ```sh claude mcp add --transport http rooyun https://www.rooyun.com/api/mcp --header "Authorization: Bearer " ``` 其他 MCP 客户端写进 `mcp.json`(Cursor 是 `~/.cursor/mcp.json` 或项目内 `.cursor/mcp.json`): ```json { "mcpServers": { "rooyun": { "type": "http", "url": "https://www.rooyun.com/api/mcp", "headers": { "Authorization": "Bearer " } } } } ``` 把 `` 换成用户那枚 `dj_pat_…`。**不要把令牌写进会被 commit 的文件**—— 项目内的 `.cursor/mcp.json` 通常会进版本库,优先用用户级配置或环境变量注入。 ### 路径 B · Skill(Claude Code 增强) **Skill 有两个版本,二选一安装。动手前先确认用户要哪个:** | 版本 | 装到 | 适合谁 | |---|---|---| | 完整版 `rooyun` | `~/.claude/skills/rooyun/` | 想让 AI 按平台流程搭完整工程:剧本 → 角色/场景/道具设定板 → 分镜 → 定嗓 → 出片。提示词由平台编译,有 15 条工作流纪律 | | 精简版 `rooyun-cli`(也叫简易版,CLI 自由模式) | `~/.claude/skills/rooyun-cli/` | 想自己写提示词、自己定顺序:只告诉 AI 有哪些命令能调、各产出什么,提示词逐字提交。只保留报价 / 幂等键 / 令牌 / 轮询四条铁律 | - **用户已经点名了版本就直接装,不要再问**:「精简版」「简易版」「自由模式」「CLI 版」「rooyun-cli」→ 精简版; 「完整版」「rooyun」→ 完整版。 - **没点名**:把上表两行各用一句话讲给用户,问他装哪个,**等用户回答再装**。别替他选,也别两个都装。 - 装之前看一眼 `~/.claude/skills/` 下有没有**另一个**版本的目录:有的话告诉用户「两个版本同时装会抢着触发」, 问他要不要删掉另一个,**得到同意再删**。 完整版: ```sh mkdir -p ~/.claude/skills curl -fsSL -o /tmp/rooyun-skill.zip https://www.rooyun.com/api/skill/bundle unzip -o /tmp/rooyun-skill.zip -d ~/.claude/skills # 落点 ~/.claude/skills/rooyun/ chmod +x ~/.claude/skills/rooyun/scripts/*.mjs # zip 里已带可执行位,此行是兜底 ``` 精简版(地址带 `?`,**必须加引号**,否则 zsh 当通配符报 no matches found): ```sh mkdir -p ~/.claude/skills curl -fsSL -o /tmp/rooyun-cli.zip "https://www.rooyun.com/api/skill/bundle?variant=cli" unzip -o /tmp/rooyun-cli.zip -d ~/.claude/skills # 落点 ~/.claude/skills/rooyun-cli/ chmod +x ~/.claude/skills/rooyun-cli/scripts/*.mjs ``` 装完: 1. 按上文「前置:登录一次」设好 `SKILL` 再跑 `dj.mjs login`,然后 `node "$SKILL/scripts/dj.mjs" me --pretty` 验证; CI 等场景也可以改用环境变量 `ROOYUN_API_TOKEN`。 2. 告诉用户**重开一个会话**才生效,并说明装的是哪个版本、两版的区别。 3. 装的是精简版:之后的行为以 `rooyun-cli/SKILL.md` 为准。本文后面的「工作流纪律」是完整版与 MCP 的规则, **精简版只强制其中第 1、3、4、5 条**(先报价、令牌不碰明文、幂等键、按建议轮询),其余流程交给用户决定。 只想读某一个文件而不下载整包(纯文本,无需鉴权): - https://www.rooyun.com/api/skill/SKILL.md - https://www.rooyun.com/api/skill/README.md - https://www.rooyun.com/api/skill/AGENTS.snippet.md - https://www.rooyun.com/api/skill/dj.mjs - https://www.rooyun.com/api/skill/download.mjs - 精简版主文件:https://www.rooyun.com/api/skill/SKILL.md?variant=cli 完整版包里有什么:`SKILL.md`(Claude Code 技能主文件)、`scripts/dj.mjs`(零依赖 CLI)、 `scripts/download.mjs`(批量下载 + 完整性报告)、`AGENTS.snippet.md`(Codex 等 其他 Agent 的等价说明,直接粘进 AGENTS.md)、`README.md`(安装与权限说明)。 精简版包:精简版 `SKILL.md` + 同一份脚本 + 四份参考文档。脚本零依赖,Node ≥ 18。 **没有 Skill 目录的宿主(Codex 等)**:完整版把 `AGENTS.snippet.md` 整段粘进项目的 AGENTS.md; 精简版把 zip 解压到任意位置,在 AGENTS.md 里写一句「RooYun 相关任务先读 `<解压目录>/rooyun-cli/SKILL.md`, 命令用同目录下的 `scripts/dj.mjs`」。 --- 这几条不是建议。其中大半管的是**用户的钱**和**用户的凭据**,靠自觉等于没有。 (纪律条文里出现的 snake_case 名字是 MCP 工具名;走 Skill 或直接调 REST 时,对应的命令与端点见下面「能力速查」——纪律本身与接入方式无关。) ## 工作流纪律(硬规则,不可自行放宽) ### 1. 任何 generate 之前必须先 precheck 报价,展示给用户并等确认 产品第一原则「成本前置透明」。没有例外——用户说「你看着办」「不用问我」也不例外,那种授权只覆盖创作决策,不覆盖花钱额度。 - 顺序固定:estimate_generation_cost 整批报价(单批最多 100 项)→ 把「合计 X 积分,当前余额 Y」原样告诉用户 → 拿到明确的「继续」→ 才逐条 start_generation。 - 报价响应自带 balance,所以报价不需要 billing:read scope,不必额外查钱包。 - 报价是体验层判断,不是扣费承诺:两次调用之间余额可能被别的任务花掉。 - 未定价模型也返回成功:必须逐项看 items[].priced 与整批的 ok,任一为假就别提交(提交也会被拒)。 - 用户中途改需求把批次变大(加镜头、换更贵的模型、加时长)→ 重新报价、重新确认。 - 链式生成要报两次价:t2i → i2v 的第二段要等第一段产物拿到 assetId 才能预检。一开始就告诉用户「这活儿分两步,我会报两次价」,不要报完第一段就默默把第二段也做了。 ### 2. 优先在你自己创建的画布上工作 用户的既有画布可能正开在浏览器里,并发写入会让某一边未同步的改动丢失。 - 默认路径:create_project 拿一张全新画布再干活。 - 确实要写用户已有的画布时,先告知并发编辑风险再动手:告诉用户「我写进去之后你那边约二十秒内会自动出现;但如果你手上有还没同步的改动,会先出一条横幅要你处理,处理不当会有一边的改动丢失」,并主动提议新建一张画布。 - 用户坚持写既有画布,就直接 apply_canvas_ops 写,**不要带 ifVersion**:不带才会在版本被推进时自动「重读 → 重放」(最多 3 轮),这正是用户同时在编辑时你要的行为;带上等于关掉这层保护,一撞就 409 失败。ops 是增量操作,重放到最新文档不会覆盖用户的改动。 - 只有一种情况该带 ifVersion:你这批操作的正确性依赖于你**刚读到的那个状态**,且该前提不在服务端校验范围内(服务端已自己兜住的不算,例如 attachAsset 的目标卡非空会直接报 card_not_empty)。这种「读—判断—写」事务宁可失败也不能重放,才用它。 - 写完提示用户「二十秒内自动出现;如果顶部出了横幅,说明你有未同步的改动,先处理它」。 ### 3. 令牌只由脚本自己读,你永远不碰明文 令牌等于用户的钱包与全部画布的写权限,一次回显就是一次泄漏。 - 令牌只存在两处:环境变量 ROOYUN_API_TOKEN,或 dj login 写下的本机凭据文件 ~/.rooyun/credentials.json;只有 dj.mjs / download.mjs 自己去读写它们。你不 cat 凭据文件、不 echo 变量,禁止把令牌写进任何文件、脚本、配置、commit、日志、报告、终端回显,禁止在命令里内联明文。 - 用户如果在对话里直接把令牌贴给你:告诉他这条消息已经泄漏了令牌,请立刻到 /profile#api 吊销,然后用 dj login --force 重新授权。不要为了「暂存」把贴来的令牌写进任何文件。 - 签名下载地址同理:短时有效期内谁拿到谁能下,不打印、不转发、不写日志。 ### 4. 批量生成必须用幂等键,失败重试复用同一个键 没有幂等键的重试就是重复扣费,而外部 Agent 的重试是常态。 - start_generation 的 requestId 是必填 uuid:每个逻辑任务生成一次并存下来(例如维护一张「镜号 → requestId」的本地表),重试时原样重发——同一个 requestId 落到同一个任务,只扣一次钱。 - 换一个新 uuid 重试 = 重复扣费。 - **那张本地表丢了怎么办**(上下文被截断、进程重启):别换个新 requestId 重发一遍——那正是重复扣费。先用 list_generations 把现场捞回来:缺省只列在途(我还有什么没跑完),也可按 canvasId 过滤(画布句柄是你一定还找得回的线索)或按 requestId 精确查。它只读库里的状态快照、不轮询上游,捞回 taskId 之后拿准确状态仍要 get_generation。 - 幂等键只认键、不认参数:同一个 requestId 配不同 prompt 重发,会原样返回第一次那个任务(首次参数生效,新参数被丢弃),既不报错也不按新参数重跑。「改了提示词要重生成」必须用新的 requestId——那是另一个逻辑任务,也会付两次钱。 - 提交限流是全域最紧的一档(10 次/分/令牌、20 次/分/账号),因为它花的是用户的钱。批量的正确姿势是「整批预检 → 逐条提交 → 轮询」,不要并发轰。 ### 5. 轮询遵守建议间隔 视频任务是分钟级的,高频轮询只会撞限流,并不会让任务更快完成。 - 按响应里 meta.pollAfterMs 的建议间隔退避(建议 3 秒起,封顶 15 秒),不要写 while 循环高频打 get_generation。 - 时长预期:视频任务分钟级;图片/音频十几秒到两分钟。 - 多条任务并行等待时各自按自己的节奏轮询,不要每秒扫一遍全部任务。 - 别自己造超时判死逻辑。服务端问不到上游时会如实回 running 下轮重试而不判死——终态一落就要同时写卡片、翻任务行、结算积分,三者必须一起发生。真卡住了由服务端看门狗统一判死并全额退回冻结积分,你会在某一轮拿到 failed。你自己判的「死」不退钱,服务端判的才退。 ### 6. 搭建完成后汇报画布链接,并提示用户打开浏览器接力 人机在同一张画布上接力是这个产品的核心价值,别把结果留成一堆本地文件。 - 链接用响应里的 url 字段拼站点地址,不要自己拼路径(首画布是 /canvas/{projectId},后建的画布多一段 canvasKey)。 - 汇报里带上实际战果与花费:几张卡、几条连线、几条产物已落卡、实耗多少积分、本令牌限额还剩多少。 - **实耗积分取轮询终态的 chargedCredits,逐笔累加**——不要拿预检报价或提交回执的 heldCredits 顶替:前者是估算,后者是冻结额(上游实耗低于估算时只扣一部分,差额已退回)。失败与取消的那几笔是 0(钱全退了),照样要如实说明「这几笔没花钱」。字段缺席时说「这笔账目未结完」,别当成免费。 - 结尾明确提示:打开浏览器可以直接继续编辑、重生成、精修。 ### 7. 下载后核对完整性元信息,缺失项如实报告 API 看到的是云端子集,静默不完整的数据会毒害用户对产物清单的判断。 - list_assets 的项目级清单带 integrity(referenced_total / cloud_available / missing)。用户浏览器里没推上云的编辑、超 50MB 的资产、上传还在队列里的,API 一概看不见。 - 有 missing 就照实说。服务端侧 missing[].reason 目前恒为 not_synced 一个值——服务端能观测到的事实只有「云端没有这一行」,为什么(超大文件 / 上传在途 / 该画布从未在登录态同步过)全发生在浏览器侧。统一处置:请用户在原设备打开该画布让它完成同步,或直接导出。 - 不要用「下载成功 8 个」掩盖「其实引用了 12 个」。 - unreadable_canvases 是另一类诚实:那些画布整档读不出、引用集未知,所以它们的引用不计入 referenced_total——意思是「不知道缺了多少」,不是「没有缺失」。 - 用了 mime 过滤时注意:integrity 按项目全量引用计算,与过滤无关,referenced_total 大于本次列出的件数是正常的,别报成缺失。 ### 8. 分析端点返回的是结构化数据,改完用 ops 回写,别重新提取 重新跑一遍分析既要用户再付一次钱,又会把他刚才在结构上做的修改整个冲掉——这是本域最容易犯、也最贵的错。 - 标准链路是「拿到结构 → 和用户过一遍改掉不对的 → 用 apply_canvas_ops 写进画布」。extract_from_script 的字段与 createCharacterBoard / createSceneSet / createPropBoard 对应;make_storyboard 的结果用 createStoryboard 落卡:kind:"video" 一段一张**分镜视频卡**(每段的 videoPrompt / durationSec / audio / cast / props 原样传回),缺省 kind 落分镜图卡。分镜视频卡只能由它派生,不要用 createCard kind:"video" 手搓(纪律 10)。 - 落卡之后要再改,就读回来接着改(read_node 拿那张卡的正文与可写参数 → updateCard 改字段),不要重跑一次分析端点。 - 分镜表卡已下线:没有 createShotTable / updateShotRow / insertShotRow / deleteShotRow 这四个 op(发过去判 400),也不要向用户提议「生成一张分镜表」。存量画布里的分镜表卡仍读得到(read_node 出全部行),但只读。 - 计费分叉要读 meta.billingUnit,别按端点猜:提取 / 分镜 / 改写是 per_round(扣积分,一次调用算一轮,与输出长度无关);反推是 per_call(图片 0.1 积分/次)或 per_second(视频 0.1 积分/秒,按时长向上取整秒),批量反推前把合计价告诉用户、失败自动退款;质检是 free(不扣积分,但照样受 scope 与限流约束)。扣积分的都带 meta.credits(反推另带 meta.creditRef),收尾汇报时算进总账。 - 视觉端点的素材一律先上传换 assetId(不收 dataURL、不收 URL、不收本地路径),单张上限约 1.4MB 且服务端不降采样——自己先压到长边 1024 量级。视频反推的多帧也要你自己抽好、逐张上传、按时序传。 - 质检结果里 checks 没有的维度 = 缺基准「没判」,不等于通过;meta.mock 为 true 时那是演示产物、没有信息量,都不要当成结论转述给用户。判 fail 后要不要重生成是一次新的花钱决策,回到纪律 1 先报价。 ### 9. 需要人工介入的步骤,签发编辑链接交给用户,说明有效期,然后去做别的事 有些事你说不清楚、只有用户能指:局部重绘要重画画面的哪一块,用文字描述永远是猜。把这一步交给他,是这条产品线上人机接力的正常形态,不是失败。 - 撞上「必须由人指定区域」的步骤就 create_edit_session 签一条链接(源图由服务端从那张卡上读,所以 nodeId 必须是一张已经出过图的卡)。 - **转述链接时必须同时说明有效期**(响应里的 expiresAt,默认 15 分钟)。只丢一条链接不说时限,用户过半小时才点开,看到的是一句「链接已过期」,而他并不知道该找谁。 - **不要阻塞轮询死等**。用户可能几分钟后才点开,也可能今天不弄了。签完链接就去做本轮还能做的别的事(或者干脆把这一轮交还给用户),下一轮再 get_edit_session 查状态——一次回合里循环等待只会白烧上下文并撞限流。 - 查到 submitted 就接着往下做:result.maskAssetId 是用户涂出来的蒙版、result.instruction 是他写的要求,拿去发起局部重绘生成。那是一次新的花钱决策,回到纪律 1 先报价再提交。 - 查到 expired / revoked 说明这条链接废了:重新签一条,并明确告诉用户上一条已失效、请用新的,别让他对着一条打不开的旧链接较劲。 - 链接等同于凭据——它免登录、谁拿到谁能编辑那张卡。只转述给用户本人,不写进文件、日志、commit 或任何公开渠道(与纪律 3 对令牌、对签名下载地址是同一条红线)。 ### 10. 按依赖顺序搭画布:剧本 → 角色/场景/道具 → 分镜视频卡 → 定嗓 → 生成;分镜视频卡只能由 createStoryboard 派生 画布不是一堆孤立的卡,是一张有向图:分镜视频卡通过本场的引用坞引用角色/场景/道具的设定板,生成时才拿得到参考图。跳步或手搓建出来的卡看着都在,实际彼此不连——用户会拿到一批「没有参考图、人物每镜都换脸」的视频,而且从卡面上看不出哪里错了。真实事故(已两次):调用方直接用 createCard 建了一堆视频卡,既没连参考也没成组,成片里的人物与设定板完全不是同一个人。 - **顺序是硬规则,不是建议**:① 剧本文本卡 → ② 角色/场景/道具设定板并出图 → ③ 拆分镜 → ④ createStoryboard kind:"video" 落分镜视频卡 → ⑤ 定嗓 → ⑥ 出视频。每一步都以上一步在画布上的产物为输入;用户说「直接出视频」也要先把前面几步补齐,跳过任何一步都会在最后一步静默出错。 - **第一步永远是把剧本落成文本卡**(create_card kind=text,正文放剧本全文)。它是后续一切的真相源:提取从它出发、改稿改它、出了问题回头核对它。手上只有一句创意时,先跟用户把剧本聊出来再落卡,不要拿一句话直接去拆分镜。 - **第二步提取素材并落卡,顺序是角色 → 场景 → 道具,然后把设定板出图**。提取用 extract_from_script(只返数据不落卡),过一遍用户意见后用 createCharacterBoard / createSceneSet / createPropBoard 落卡;再用卡上编译好的提示词(纪律 11)逐板 t2i 出定妆照 / 全景 / 道具图——分镜视频引用的是设定板的**产物**,板没出图 = 参考为空,连了线也等于没连。这是第一次报价(纪律 1)。 - **第三步才拆分镜**(make_storyboard,整集一次拆完),并把上一步落卡的名字作为 castNames / sceneNames 传回去——让镜头里的人名与画布上的卡逐字对齐。同时把每个角色的 shotInjection 映射成 appearanceAnchors 传入,否则模型每个镜头都在重新想象这个角色长什么样。 - **第四步落分镜视频卡,只能用 createStoryboard kind:"video"**:每场一批,params 带 sceneName + layout:"grouped",shots 逐段取该场 segments[] 的 {title, videoPrompt→prompt, durationSec, audio, cast, props} **原样传回**(段的并集与音轨标记由服务端算好)。落出来是站内「提取分镜」同款:本场参考收进引用坞放最左列、视频卡排网格、整块圈进以场景名为标题的编组。**禁止**用 createCard kind:"video" 建分镜视频卡(不匹配参考、不进坞、不成组)、**禁止**把 shots 逐镜落成视频卡(段才是生成单位)、**禁止**自拼 prompt。op 结果的 noop 点名了「声明了却没有设定板」的名字时,回到第二步补建再落。 - **第五步:有台词的角色先定嗓**(角色音色绑定),再批量出对白镜头——否则同一角色每镜换一把嗓子,且事后无法补救(只能重出)。 - **第六步出视频前先跑画布体检,再核参考**:lint_canvas(CLI:dj canvas lint ;只读、不扣积分)。**有 error 级 finding 就不许提交生成**——按每条 message 修画布(补连线用 connectCards、缺的设定板先补建、板没出图先把板生成出来),改完重跑到 summary.errors 为 0。它把「声明了角色却没有任何参考连线」这类静默失配变成确定性结论。再核参考:read_canvas 看每张分镜视频卡的上游是不是本场引用坞(hub 卡的 members 列出它引用的设定板)。生成走 ref2v,但**不必自己凑 referenceAssetIds**——只要带上 canvasId + nodeId 且请求里不给 referenceAssetIds,服务端就按画布边(直连图边 + 引用坞成员展开)自动解析参考,并在回执的 meta.resolvedReferences 里回显每一张的来源(assetId / sourceNodeId / via / label)。**回显必须逐条核对**:与你预期的设定板对不上,说明画布连线不是你以为的样子,先去改边再生成,别把这一单跑完。提示词仍要用 read_node 读回卡上的原文。** 的编号不要猜**:lint_canvas 的响应里就有 references 预演,每张视频卡逐条给出 index / tag / label,顺序与真正提交时逐位一致——照抄即可,不必先花钱生成一次去看回执(写法见 SKILL.md「视频提示词书写规范」章)。没有任何参考连线的视频卡**不要生成**(服务端解析为空时会照常报「缺参考」)。errors 归零后才轮到报价——这是第二次报价(纪律 1)。 - 已经跳步建了一堆孤立的卡怎么办:**不要删了重来**(会丢已生成的产物)。缺设定板的先补建并出图,缺边的用 connectCards 把设定板连到视频卡上,再继续。 - 用户明确要求「只要剧本,先别建东西」时,就只调分析端点、只把结构给他看——analyze 系列默认不落卡,正是为这种「先看后建」准备的。 ### 11. 每次生成前复查提示词,别把没检查过的字符串送去烧钱 生成是不可逆的花钱动作,而提示词的问题几乎都能在提交前一眼看出来:角色名写错连不上参考图、对白漏了整条音轨、定妆照被写成剧情画面。事后发现只能重新花钱再来一次。 - **角色定妆照不要自拟提示词**。createCharacterBoard 落卡时平台已经编译好一份(单主体 / 中性表情 / 干净影棚背景 / 三视图 / 禁多余人物与文字),用 GET /v1/canvases/{id}/nodes/{nodeId} 读回正文原样提交。自己拿角色的外貌描述拼一段,会把这些约束整体旁路掉,出来的图混剧情、带别人、不是三视图,而它正是后面所有镜头的参考基准。 - **视频提示词就是分镜视频卡上的 prompt**(createStoryboard kind:"video" 落卡时来自 analyze/storyboard 的 segments[].videoPrompt):生成前 read_node 读回原样提交,不要自己按 shots 拼、也不要只在请求里临时改写。平台编译过的版本里,多镜段带硬切指令与时间码,对白在「音轨:」行内一字不丢;自拼最常见的后果就是整片没有对白。 - **提交前逐条核三件事**:① 提示词里出现的角色/场景名与画布上卡的名字逐字一致(差一个空格就连不上);② 这一镜声明的角色、场景、道具都已经连上参考图;③ 有对白的镜头,对白确实在提示词里。 - **不要留代称和占位符**:「她」「那个男人」「主角」在跨镜生成里指代不到任何人,模型只会每镜随机编一个。要么写全名,要么靠 cast 显式绑定。 - 复查发现问题就先改画布(updateCard 改卡上的提示词),再提交生成——改完的提示词要落回卡上,而不是只在这次请求里临时改。否则用户在浏览器里看到的还是错的那版,重新生成又会错一次。 - **视频粒度的提示词服务端会替你补齐**(ref2v/i2v/flf2v + canvasId + nodeId,缺省开):没写 时补参考绑定声明、上游分镜有台词而你没写时补台词段、没写【限制】时按上下文补 3-8 条。只追加不改写、幂等,补了什么在回执 meta.promptCompile.applied 里如实回显(没有这一位 = 一个字都没动)。所以上面第三件事「有对白的镜头对白在提示词里」现在有了兜底,但**别因此不检查**——补台词只在上游分镜图卡带 sourceShot 台词时才有料。要逐字可控(白模换角的动作锁定句、定嗓的音色缓解条款)就传 params.promptCompile:"off"。 - **回执的 meta.warnings 必须看**:它点名「提示词里点了某个设定板的名字、但这张卡没连它」。提交照常成立(纯文生是合法用法),但那正是「人物每镜换脸」事故的成因——看到就停下来改边,别等出片。 ### 12. 视频默认走 Seedance 2.0 Fast 720p;余额不够或调用失败时,把「换 MiniMax H3」和「充值」两条路摆给用户选 用户没点名模型时,靠目录里的档位说明猜,每次选的都不一样、报价也跟着漂;而余额不够或上游失败时最常见的两种错误处置是「静默换个便宜模型再提交」(花的仍是用户的钱、成片效果还不一样)和「只说一句失败了」(用户不知道下一步能做什么)。 - **默认模型**:i2v / flf2v / ref2v 三种视频粒度,用户没点名模型时一律用目录里名字以「Seedance 2.0 Fast」开头的那一行(list_models 按 capability 现取 id,别硬编 uuid),并且 estimate_generation_cost 与 start_generation 的 params **都显式带 resolution:"720p"**——它的目录默认档是 480p,不带就按 480p 出片、按 480p 计价,报价与成片都不是用户以为的那一档。 - 用户点名了模型或档位就按用户的(显式选择优先),报价与确认流程不变。 - 三种「跑不动」都要停下来把两条路摆给用户,**不要自行换模型、不要换个 requestId 盲重试**:① 报价 ok=false 且各项 priced=true(available 小于 total;最常见成因是用户手里只有赠送积分,而赠送积分不覆盖 Seedance);② 报价或提交回 402 insufficient_credits;③ 任务终态 failed,或提交被 upstream_rejected / service_unavailable 拒绝。 - 话术固定给两个选项:「这批用 Seedance 2.0 Fast 720p 需要 X 积分,当前可用 Y,跑不动(或:上游调用失败,这一笔没扣钱)。A. 换 MiniMax H3——目录里名字以「MiniMax H3 ·」开头、能力含本粒度的那一行,平台自建算力、单价更低、赠送积分可用,我会重新报价再提交;B. 去网页端充值,我继续用 Seedance。」然后等用户选,别替他选。 - 选 A 是一次新的报价(纪律 1):H3 也有 720p 档,resolution 照给;多镜硬切段 H3 照收不拒,但它偏向按单镜理解,段内构图变化密集时可能不切镜——这是表现倾向不是接口限制,别默认替用户拆镜,先拿代表性的一段实测再定。别挑「MiniMax H3 Max」——那是三方渠道,比 Seedance 还贵。 - 402 token_limit_exceeded 不在本条范围:换模型没用、充值也没用,按「两个 402 千万别合并」处置(调令牌额度或换令牌)。 ### 13. 多轮 Agent 会话:会话 id 是状态、一个 Agent 一个会话、每轮先按目录报价、跑不动不换 Agent 会话的历史、系统提示词与窗口化全在服务端,调用方手里只有一个 conversationId——它就是这段创作的全部状态。丢了它、或者遇事就新建一个会话,等于把上下文扔了重头再说一遍:用户既要重付每一轮的钱,拿回来的稿子还换了个基准。 - **你只持有 conversationId,历史在服务端**:每轮只发新的一句话,不要把上一轮的稿子、要求、素材再贴一遍。本地维护一张「任务 → conversationId」表(与「镜号 → requestId」同一张表的思路);表丢了用 list_conversations(CLI:dj chats)把现场捞回来,**不要新建会话重头说一遍**。 - **一个会话绑定一个 Agent**:agentId 在创建会话时定死,**中途不可换**。换 Agent = 新建一个会话;多个 Agent 并行做事 = 多个会话并行,各自互不串上下文。 - **每轮都扣钱,所以每轮都先报价**:报价**直接读**建会话 / 查会话响应的 meta.estimatedTurnCredits(= billing.stagesPerTurn × 这个会话 modelId 的 per_round 单价,服务端已经乘好,CLI:dj chat new / dj chat show 的「计费」那一行),不必自己算、也不必再拉一次 Agent 目录。把「这一轮预计 X 积分,当前余额 Y」告诉用户、拿到明确的「继续」,才 send_message(与纪律 1 同则,没有例外)。 - **estimatedTurnCredits 为 null = 这个模型未配价,发轮会被 503 拒**——那不是免费、也不是余额问题。如实告诉用户「该模型未配价、提交会被拒」,换个模型(同一会话可换 modelId)或请管理员补配价格;**不要因为算不出价就跳过确认直接发轮**。 - **每轮 requestId 必填,重试复用同一个键**(与纪律 4 同则):同键重放返回**同一轮**的结果且不再扣费;换个新 uuid 重发 = 又付一轮的钱。 - **409 conversation_busy 是「这个会话正忙」,不是「这轮失败了」**:成因是上一轮还没跑完,或用户正在网页端同一个会话里写。按 meta.retryAfterSeconds 退避,然后**用同一个 conversationId、同一个 requestId 原样重发**;不要新建会话重发,也不要并发再打一轮。 - **402 insufficient_credits 时把两条路摆给用户等他选**:A. 换模型(**同一个会话可以换 modelId**,下一轮生效,按新单价重新报价);B. 去网页端充值。**不要换 Agent、不要自行降级到更便宜的 Agent、不要把会话重开**——那是在替用户改创作基准。 - **稿件正文与产物只从响应的 artifacts[] 取**(A/B 两稿逐稿带 variantId):不要让用户把稿子贴回来,也不要自己复述一遍再当下一轮的输入。要指定「就用哪一稿」,下一轮用 references 带上那个 artifactId 即可——服务端自己去取最新正文。 ### 14. 图片默认走 Qwen-Image-2.1 · GPU;固定 0.05 积分/张,画质与档位都不改价 与视频同一个毛病:用户没点名模型时靠目录里的档位说明猜,每次选的都不一样、报价也跟着漂。而这个默认是**自建 GPU、按张固定计价**的,与此前按 token 计费的 GPT Image 2.5 口径完全相反——把旧经验(2K 更贵、赠送只覆盖 1K、别传 quality)套上来,就会在本不该停的地方停下来劝用户降档,白白折损成片规格。 - **默认模型**:t2i / i2i 都用目录里名字以「Qwen-Image-2.1」开头的那一行(list_models 按 capability 现取 id,别硬编 uuid)。两个粒度是**同名两行**,按 capability 各取各的。同一套权重:不连参考图就是文生图,连上参考图就是按图编辑。 - **计价固定 0.05 积分/张**:1K 与 2K 同价、low/standard/high 三档同价、文生图与编辑同价。**不要为了省钱劝用户降档**——降档只让出图快一点,一分钱也省不下来。 - **档位只有 1k / 2k,没有 4K**:传 size:"4k" 会回退到模型默认档(**2k**),报价不变但你以为的 4K 不会发生。不带 size 就是 2k。 - **quality 可以传,而且是真参数**:自建 GPU 通道把它映射成采样步数(low = 文生图 15 / 编辑 18,不传 = 模板默认 25,high = 35),high 真的更精细、只是更慢,**且不加价**。这与旧版「别传 quality」的结论相反,按本条执行。 - **赠送积分全覆盖**:自建 GPU 模型一律在赠送范围内,不存在「赠送只覆盖 1K」那道坎。用户手里只有赠送积分照样能出 2K。 - **参考图最多 9 张**(顶层 referenceAssetIds,超出直接 400;模型本身支持到 10,卡在 API 上限)。提示词里用 … 指派每一张的用途,编号即上送数组下标。局部重绘的蒙版规则不变(mask 只有 t2i 消费;模型不吃蒙版会回 422 mask_unsupported)。 - **跑不动时同纪律 12 的两条路,别自己换模型**:报价 ok=false、402 insufficient_credits 或任务 failed 时,把「A. 换 GPT Image 2.5(目录里名字以「GPT Image 2.5」开头、能力含本粒度的那一行,按 token 计费、赠送只覆盖 1K,我会重新报价再提交)/ B. 去网页端充值」摆给用户选,等他点头再换——换模型是创作决策,不要替他选。 - 用户点名了模型或档位就按用户的(显式选择优先),报价与确认流程不变。 ### 15. 博主分析一律点名 videoIds,别让服务端自动挑样本 抖音自 2026-09-15 封禁了博主主页作品列表接口(Argus 安全插件,游客态恒 403),自动挑样本那条路**拿不到候选池所以必然失败**。而单条视频解析完全正常——点名 videoIds 的模式不依赖主页列表,是现在唯一走得通的路。维护者已定调:不做签名逆向,Agent 一律点名。 - **发起分析永远带 videoIds**(2–12 条)。不带 = 让服务端自动挑 = 撞 502 `creator_upstream_failed`,白等一轮。 - **videoId 从哪来**:向用户要**单条视频**的分享链接(抖音 App 里逐条「分享→复制链接」),每条用 `creator-resolve` / `video-url` 解析出 `videoId`。别指望从主页链接拿列表。 - **secUid 从哪来**:拿博主**主页**链接调 `creator-resolve`。列表被封时它仍回 200,但 `videos` 是空的且带 `videoListUnavailable` —— `creator.secUid` 照样可用,这正是它降级成 200 而不是 502 的原因。看到这个字段就别再等列表了,直接去要单条视频链接。 - **空 `videos` 不等于「这个博主没作品」**:带 `videoListUnavailable` 是拉不到,404 才是真的没有。别把前者报告成后者,那是在骗用户。 - **点名的作品会被逐条核对**:进不了样本的会带 `reason` 出现在响应的 `skippedVideoIds` 里。`unresolvable`(已删除/私密/风控)、`not_analyzable`(时长不在 0–600 秒)、`note`(图文笔记)、`foreign_creator`(作者不是这个博主,已排除以免污染其画像)这四种重发也没用;**`timeout` 与 `throttled` 只是这次没轮到,稍后重新点名这几条即可进**。如实转告用户哪几条没进去、为什么,别默认全都分析了。可用样本不足 2 条时整体报错:**全因 timeout/throttled 则是 503(原样重发即可),否则 400(要换 videoIds)**。 - `video-url` 传**博主主页**链接同样拿不到列表(与 resolve 同一条上游腿)。传**单条视频**链接则一切正常。 --- ## 能力速查 所有请求带 `Authorization: Bearer `。所有响应形状恒定—— 成功也带 `error: null`,失败也带 `data: null`: ```jsonc { "ok": true, "data": { /* … */ }, "error": null, "meta": { /* 可选 */ } } { "ok": false, "data": null, "error": { "code": "not_found", "message": "画布不存在" } } ``` **一律按 `error.code` 分支,不要按 HTTP 状态码,更不要按 `message` 文案。** 两个 402 千万别合并处理:`insufficient_credits` 是账户余额空了(去充值), `token_limit_exceeded` 是这枚令牌的消费限额用尽(去调额度或换令牌, **充值没用**)。 ### 端点表 | 方法 | 路径 | 用途 | 所需 scope | |---|---|---|---| | GET | `/api/v1/me` | 令牌自省:userId、scope、限额余量。开工第一件事 | `任何有效令牌` | | GET | `/api/v1/models` | 模型目录与单价(可按 capability 过滤) | `任何有效令牌` | | GET | `/api/v1/voices` | 音色目录(配 t2a 用;kind=system 填 voiceId,kind=user 填 voiceName) | `任何有效令牌` | | GET | `/api/v1/digital-humans` | 专属数字人目录(avatar 口播的 params.digitalHumanId 从这里取)。仅管理员可见,非管理员恒为空列表 | `任何有效令牌` | | POST | `/api/v1/projects` | 建项目(同时得到首画布 canvasId 与浏览器 url) | `canvas:write` | | GET | `/api/v1/projects` | 项目清单 | `projects:read` | | POST | `/api/v1/projects/{projectId}/canvases` | 在既有项目下再建一张画布 | `canvas:write` | | POST | `/api/v1/canvases/{canvasId}/ops` | 执行 ops 批次(1–50 个,整批原子):建卡、连线、改卡、删卡、布局 | `canvas:write` | | GET | `/api/v1/canvases/{canvasId}/structure` | 读回结构:version、nodes[](含 nodeId/title/assetIds)、edges[] | `projects:read` | | GET | `/api/v1/canvases/{canvasId}/lint` | 画布体检(只读、不扣积分):视频卡缺参考、被引用的设定板还没出图、空引用坞连了下游、提示词点了设定板的名字却没连它(改过 prompt 后最容易撞上)。**有 severity=error 的 finding 就别提交生成**——纪律 10 的门禁。响应还带 references:每张待出片视频卡的参考解析预演(index/tag/label),顺序与提交时逐位一致——提示词里的 照它抄,别猜编号 | `projects:read` | | GET | `/api/v1/canvases/{canvasId}/nodes/{nodeId}` | 详读单卡:正文全文(不截断)、可写参数快照、直接上下游(存量分镜表卡另出全部行)。精确改卡前先读它 | `projects:read` | | GET | `/api/v1/canvases/{canvasId}/character-voices` | 读角色定嗓:bindings(已定嗓)+ knownCharacters(画布上全部角色),两者相减 = 谁还没定嗓。有台词的剧出正片前先跑一次 | `projects:read` | | PUT | `/api/v1/canvases/{canvasId}/character-voices` | 按角色名增量定嗓 / 解绑(值传 null 解绑)。角色名要与角色卡逐字一致,否则 404 `character_not_found`——不一致不会报错,会在生成时静默不注入音色 | `canvas:write` | | POST | `/api/v1/analyze/extract` | 剧本 → 结构化角色/场景/道具(`kind` 三选一)。只返数据不落卡,**消耗积分** | `llm` | | POST | `/api/v1/analyze/storyboard` | 剧本 → 结构化分镜(场景→镜头两级,一镜十几个字段)。只返数据不落卡,**消耗积分** | `llm` | | POST | `/api/v1/analyze/rewrite` | 改写/优化/翻译/写词(`mode` 四选一),产物即文本。**消耗积分** | `llm` | | POST | `/api/v1/analyze/caption` | 图片/视频反推提示词(`kind` 二选一,素材传 assetId)。**扣积分**:图片 0.1/次、视频 0.1/秒,失败自动退款;受限流 | `llm` | | POST | `/api/v1/analyze/inspect` | 分镜图质检(崩坏/一致性/符合度)。**不扣积分**,但受限流 | `llm` | | GET | `/api/v1/agents` | Agent 目录(只是「平台有哪些分析能力」,不是「你能点名调哪些」,见下) | `任何有效令牌` | | POST | `/api/v1/generations/precheck` | 成本预检(花钱前必做,一次最多 100 项) | `generate` | | POST | `/api/v1/generations` | 提交生成(必带 requestId 幂等键,可定向落卡) | `generate` | | GET | `/api/v1/generations` | 列生成任务(**状态丢了就用它**):缺省只列在途,回答「我还有什么没跑完」。可按 canvasId / requestId 过滤。只读库里的快照、**不轮询上游**——要准确状态仍走下面那条 | `tasks:read` | | GET | `/api/v1/generations/{taskId}` | 轮询任务状态与进度,终态带 assetId / landedTo | `tasks:read` | | POST | `/api/v1/generations/{taskId}/cancel` | 请求取消(只有还在排队的能真停住) | `generate` | | POST | `/api/v1/edit-sessions` | 签发免登录编辑链接交给用户涂(局部重绘选区)。**转述时必须一并说明有效期**,然后去做别的事 | `canvas:write` | | GET | `/api/v1/edit-sessions/{id}` | 查用户涂完了没:pending / submitted(`result.maskAssetId` 是蒙版)/ expired / revoked | `canvas:write` | | DELETE | `/api/v1/edit-sessions/{id}` | 撤销一条还没被用的链接(幂等;已提交的返 409 `session_consumed`) | `canvas:write` | | GET | `/api/v1/assets` | 资产清单(可按项目过滤) | `assets:read` | | POST | `/api/v1/assets` | 上传本地素材(multipart,字段名 file,必带 Content-Length;≤50MB,1 GiB/天/令牌) | `assets:write` | | GET | `/api/v1/assets/{assetId}` | 资产元数据 | `assets:read` | | GET | `/api/v1/assets/{assetId}/download` | 下载产物:返回 302 签名地址(必须跟随重定向),签名 60 秒有效 | `assets:read` | | GET | `/api/v1/projects/{projectId}/assets` | 项目资产清单(多带一份 integrity 完整性元信息) | `assets:read` | | GET | `/api/v1/billing/wallet` | 钱包余额与冻结额 | `billing:read` | | GET | `/api/v1/billing/ledger` | 账单流水 | `billing:read` | | POST | `/api/v1/creators/resolve` | 解析抖音博主/视频分享链接 → 元数据(按 kind 判别;**不含播放直链**,读产物只需 id) | `creators:read` | | GET | `/api/v1/creators/{secUid}` | 博主画像全量报告(详细资料 + 仿写提示词 stylePrompt/replicationGuide + freshness)。仅限本账号发起过分析的博主 | `creators:read` | | GET | `/api/v1/creators/{secUid}/videos/{videoId}/report` | 单视频多模态报告 + ASR 转写全文(授权覆盖你分析过的视频与画像深度样本) | `creators:read` | | POST | `/api/v1/creator-analyses` | **发起**一次博主分析(异步,返回 jobId)。整条链 30–60 分钟;每账号同时只允许一个在跑;带 requestId 可幂等 | `creators:read` | | GET | `/api/v1/creator-analyses/{id}` | 轮询分析进度(15–30 秒一次即可)。终态 succeeded/partial/failed——**partial 不是失败**:逐条报告已可读,只是没有账号画像 | `creators:read` | | POST | `/api/v1/reference-decompositions` | **发起**一次参考视频拆解(异步,返回 jobId):一条抖音单条视频 → 逐镜头的「分段实现账」(每镜的叙事功能/意图/可改编维度/画面描述)。约 5–15 分钟;带 requestId 可幂等 | `creators:read` | | GET | `/api/v1/reference-decompositions/{id}` | 轮询拆解进度(15–30 秒一次)。succeeded 时 `result.account` 直接就是产物(无需再读别的端点);`visual` 为空串 = 该镜没有画面证据,不是缺陷 | `creators:read` | | POST | `/api/v1/reference-decompositions/{id}/adaptations` | **同步**改编一条已拆解的参考视频:给要换的维度(主播/产品/场景/垂类…),服务端逐段按该段的 adaptable/preserve 决定改不改——命中 preserve 的不改并在 conflicts 里标出;function/intent/段数由服务端回填,模型只写新台词与新画面要求。先看 verdict:strained = 某个维度过半的段都拒绝更换,产物是自相矛盾的混合稿,该换对标片而不是硬用 | `generate` | | POST | `/api/v1/reference-decompositions/{id}/compare` | **复刻验收**(纯计算、不花钱):把自己成片的拆解与参考片的拆解做结构 diff——功能序列 LCS 对齐、参考片有而成片没有的功能、必保维度有没有保住。verdict 只是提示不是门禁(只比叙事结构,不比观感画质) | `creators:read` | | POST | `/api/v1/reference-templates` | 把 2–12 条已拆解的参考视频归纳成一条赛道的**结构模板**(纯计算、不打上游、不花钱):骨架序列、每个环节的出现率(< 1 说明不是每条都有)、功能组成占比、各维度「换掉就垮」的共识比例 | `creators:read` | | POST | `/api/v1/creators/{secUid}/videos/{videoId}/archive` | 把视频**原片**转存进你的资产库 → 拿 assetId 走 /v1/assets/{id}/download 取文件。幂等;单文件 200MB、每账号 50 份上限。⚠️ 取文件还要 assets:read,两个 scope 都要勾 | `assets:write(+ assets:read 取文件)` | | GET | `/api/v1/briefs/latest` | 每日频道简报:选题 + 相关性依据 + **可执行性判断**(materialNeed:pure_oral 纯口播可做 / needs_broll 要 B-roll / needs_shooting 要实拍 / unknown 未判)+ 对标参考的结构拆解。`?format=markdown` 直接出可给用户看的全文(v1 唯一的信封例外,只在 200 上成立)。写稿的起点,**九成情况只需要这一条** | `brief:read` | | GET | `/api/v1/briefs` | 历史简报列表(游标分页,offset 封顶 400)。`date=YYYY-MM-DD` 精确取某天 | `brief:read` | | GET | `/api/v1/briefs/{id}` | 单份简报全文。id 收 uuid **或** `YYYY-MM-DD` 两种形态;同样支持 `?format=markdown` | `brief:read` | | POST | `/api/v1/briefs/rebuild` | 重建当日简报。不花钱、不调模型(只走关键词闸);幂等(一账号一天一份,覆盖同行)。⚠️ date 只能今天或昨天(UTC),且统计窗口**恒为此刻往前 24 小时**——指定昨天只是把结果落到昨天那一行,不是时光机 | `brief:write` | | GET | `/api/v1/radar/sources` | 我订阅的对标博主 + 粉丝/作品数与 7 日增量。`followerDelta7d: null` = 序列不足以推断,**不是没涨** | `radar:read` | | POST | `/api/v1/radar/sources` | 按**主页链接**订阅一位博主(不收 secUid,平台与 id 由服务端解析;夹着链接的分享文本也行)。幂等。支持抖音 /user/、B 站 space.bilibili.com/、YouTube /channel/UC…;不支持 b23.tv 短链与 YouTube @handle | `radar:write` | | DELETE | `/api/v1/radar/sources/{id}` | 退订。只删你的订阅行,全局源与历史快照序列都保留(重新订阅能接上曲线)。**不幂等**:重复删返回 404 | `radar:write` | | GET | `/api/v1/radar/sources/{id}/followers` | 粉丝数日粒度序列(不插值;`followerCount: null` = 那轮没采到,不是掉到 0) | `radar:read` | | GET | `/api/v1/radar/items` | 订阅博主的作品,按**窗口内增量**排序(`*_velocity`)而不是绝对值——绝对值排序会让大号永远霸榜。窗口按条目年龄收窄:`delta.full: false` 时数值**只是下界**,与满窗口的条目直接比大小是错的 | `radar:read` | | GET | `/api/v1/radar/items/{id}` | 单条作品详情。`meta.medianRatioUnavailable` = 条目已滑出该博主扫描面、中位比算不出(不是表现差) | `radar:read` | | GET | `/api/v1/radar/items/{id}/metrics` | **原始**指标快照序列:不插值、不补零、不平滑,采样间隔也不均匀(48h 内每小时 / 14 天内每 6 小时 / 之后每日)。两点之间的空档是「没采到」,不是 0。抖音 `views` 恒 null,`coins` 仅 B 站 | `radar:read` | | GET | `/api/v1/radar/hot` | 热榜条目 + 生命周期序列。`rank` 只在热度榜上有意义(时序榜的第 1 只代表最新),`hotValue` 跨榜不可比;掉榜表现为序列断点而非 rank:null | `radar:read` | | GET | `/api/v1/radar/daily` | 每日选题报告:爆款、涨掉粉、推荐选题,**自带一份成稿 markdown** 可直接给用户 | `radar:read` | | GET | `/api/v1/copy/entries` | 检索文案库——别人的爆款**是怎么写的**:转写全文 `body` + 钩子行 + 段落节奏(role + 时长占比)+ 金句 + CTA + 标题公式。**拿结构写自己的稿,别照搬原文**。⚠️ 检索**只匹配钩子行**(正文不参与检索);别人的**用户私有条目**不给正文;tier 是**集内分位**,跨集比没有意义 | `copy:read` | | POST | `/api/v1/copy/entries` | 导入自己的一条文案。幂等键 = 全库唯一的内容哈希(重复导入收敛到同一行,`meta.deduplicated: true`);正文已属平台或别人则 409 copy_duplicate。质检标记只提示不拦截 | `copy:write` | | GET | `/api/v1/copy/entries/{id}` | 文案详情,形状与列表一行完全相同。别人的用户条目一律 404(不用 403) | `copy:read` | | GET | `/api/v1/copy/jobs` | 自动提取队列进度。⚠️ **只列你自己触发的**任务,平台级自动排队的不出现——空列表不代表没在跑 | `copy:read` | | POST | `/api/v1/copy/jobs/{id}/retry` | 重试失败的提取。**只有 `failed` 能重试**,其余阶段返回 409 copy_job_not_retryable(`meta.stage` 给当前阶段) | `copy:write` | ### LLM 分析能力(`llm` scope) 五条端点把「读剧本 / 读素材 / 改文案」这些原本只在网页里的能力开出来了。 **它们默认只返回数据、不落卡**——这是刻意的:你要的是「拿到 → 判断 → 修改 → 再决定要不要落」,直接落卡等于剥夺中间那步。落卡走 ops。 **核心用法(这是本域全部价值所在)**: ``` 提取/拆分镜 → 拿到结构 → 你和用户过一遍、改掉不对的 → 用 ops 回写画布 ``` 改完想再改,就**读回那张卡接着改**(详读端点 + `updateCard`), **不要重新提取一遍**——重来一次既付第二次钱,又会把用户刚才的修改冲掉。 **计费分叉,读 `meta.billingUnit` 分辨,别按端点猜**: | 端点 | 计价 | 说明 | |---|---|---| | `analyze/extract` · `analyze/storyboard` · `analyze/rewrite` | `per_round` | **扣积分**。一次调用算一轮,与输出长度无关——拆分镜输出上万 token 也算一轮 | | `analyze/caption`(`kind: "image"`) | `per_call` | **扣积分**:0.1 积分/次,与网页端同价 | | `analyze/caption`(`kind: "video"`) | `per_second` | **扣积分**:0.1 积分/秒,计费时长 = max(`durationSec`, 末帧时刻) 向上取整秒,上限 600 秒 | | `analyze/inspect` | `free` | **不扣积分**,但照样受 `llm` scope 与限流约束 | 扣积分的都带 `meta.credits`(本次实耗)与 `meta.creditRef`(对账凭据), 文本三条另带 `meta.usage`(真实 token),收尾汇报时把它们算进总账。 反推调上游前一次扣费,502 失败**一律已自动退款**(`meta.credits` 为 0); 余额或令牌限额不足回 402,平台未给反推配价回 503(不是余额问题)。 `meta.mock = true` 的演示产物不扣费(`billingUnit` 为 `free`)。 **素材必须先上传拿 assetId。** `caption` / `inspect` 只收 `assetId`(64 位小写 hex), **不收 dataURL、不收 URL、不收本地路径**——本地文件先走 `POST /api/v1/assets`(multipart,需 `assets:write`)换成 assetId 再传进来。 单张上限约 1.4MB 且**服务端不降采样**,超了直接 413:请自己先压到长边 1024 量级。 视频反推的多帧也要你自己抽好、逐张上传、按时序传 assetId(服务端不抽帧)。 三条专属错误码(都是 502):`llm_upstream_failed`(上游挂了)与 `llm_empty_output`(上游没吐字)**已自动退款**,可直接重试; `llm_unparsable_output`(有输出但结构不对)在文本三条上**已计费不退款**—— 模型确实烧了 token,只是格式没对上,通常重试即可(反推例外:产物不可用同样退款)。 关于 `GET /api/v1/agents`:它列的是**平台有哪些分析能力**, **不是**「你可以点名调哪个 Agent」。分析端点用哪个 Agent 由服务端硬编码 (提取恒 `agent.extract`、分镜恒 `agent.storyboard`、改写按 `mode` 查表), 请求体里传不进 Agent 名字,目录里的其余 Agent 只在网页端可用。 **没有通用对话通道**,别去找。 ### ops 词表(17 个) `POST /api/v1/canvases/{canvasId}/ops`,请求体 `{ "ops": [ … ] }`, 一批 1–50 个,**整批原子**:任何一个不过,整批都不落盘。 | op | 一句话 | |---|---| | `createCard` | 建通用卡(kind: text/image/video/audio/scene) | | `createCharacterBoard` | 建角色设定板(带身份锚,分镜会按名字自动连参考边过来) | | `createSceneSet` | 建场景卡(带身份锚,同样参与分镜自动连线) | | `createPropBoard` | 建道具板(不参与自动连线,要手动 connectCards) | | `createStoryboard` | 建分镜:一个 shot 落一张卡(缺省图卡;kind:"video" 落分镜视频卡=站内「提取分镜」同款),返回的 nodeIds 与 shots 一一对应。每镜带 cast/props,按场次带 sceneName + layout:"grouped"。分镜视频卡只能由它派生,别用 createCard 手搓 | | `connectCards` | 连线(已存在返回 noop,不重复创建) | | `disconnectCards` | 断线(本就没有这条线也返回 noop;可选 sourceHandle 只断该端口) | | `updateCard` | 改卡(可改字段**按卡型**不同,见下表) | | `attachAsset` | 把资产库里的产物绑到一张**空卡**上(`{nodeId, assetId}`)。用于产物只落进资产库、没落到画布的降级场景——不必重新生成、不必重复扣费。资产必须是你自己的云端资产,且大类与卡型一一对应(image→image 卡);目标卡已有产物/版本链/在途任务一律拒绝(`card_not_empty`,覆盖会让原产物被资产回收误删),此时先 createCard 再绑 | | `adoptVersion` | 同一张卡生成过多次时切换采用哪一版(`{nodeId, assetId}`,assetId 只能取自这张卡详读返回的 versions[];不在版本链里报 version_not_found,已是当前版返回 noop) | | `moveCard` | 挪动已有卡片(`{nodeId, position}`;顶层卡给画布绝对坐标,组内卡给相对编组左上角的偏移。不改层级、不做避让;用户锁过位置的卡报 card_locked) | | `deleteCard` | 删卡 | | `autoLayout` | 自动布局。不给参数 = 整画布按连线分层(与以往一致);`{scope:{nodeIds}}` 只整理这几张顶层卡、`{scope:{groupId}}` 只整理该编组内的卡;`mode` 选 `flow`(按连线分层)/ `grid`(网格)。整画布与局部整理都跳过用户锁了位置的卡(结果 noop 里点名 card_locked);只改坐标,不改卡片宽高 | | `groupCards` | 成组(`{nodeIds: 2-50 个, title?, clientRef?}`):只收与首张同层的卡,成员视觉位置不变;锁定卡跳过。clientRef 指向新编组,同批后续 op 可引用 | | `ungroup` | 解组(`{groupId}`):子卡留在原位提升到上一层,组框删除;目标不是编组报 invalid_target | | `packHub` | 打包成引用坞(`{nodeIds, title?, clientRef?}`):有产物的卡进坞(原卡不动),没产物的跳过;全都没产物报 invalid_target | | `pruneHubMembers` | 清理引用坞失效成员(`{hubId, memberIds?}`):摘掉目标卡已被删的成员及其行级端口边,不碰其他卡;不给 memberIds = 清全部失效成员;memberIds 里有仍有效的成员或目标不是引用坞报 invalid_target | 两条必踩的规矩: - 引用的 nodeId 必须在这批之前就已存在。节点 id 由服务端生成,只能从响应里取 (`results[].nodeIds` / `createdNodeIds`)——**对刚建的卡连线/改卡必须放到下一批**。 - **默认不要给 `ifVersion`**:不给,服务端在版本被推进时会自动「重读 → 重放」(最多 3 轮), 用户正开着画布编辑也照样写得进去;给了就关掉这层保护,一撞就 409 `version_mismatch`, 盲目重试无意义。只有「读—判断—写」事务(正确性依赖你刚读到的状态、宁可失败也不能重放) 才给,取上一次响应的 `data.version`,**不要硬编码**(每批 +1,起点未必是 1)。 **分镜表卡已下线**:`createShotTable` / `updateShotRow` / `insertShotRow` / `deleteShotRow` 四个 op 已撤除(发过去判 400)。拆分镜用 `createStoryboard` (一镜一卡:缺省图卡,`kind:"video"` 落分镜视频卡),某一镜要改用 `updateCard`。 存量画布里的分镜表卡仍读得到,但只读。 ### `updateCard` 按卡型放行(最容易踩的一处) | 卡型 | `updateCard` 可改字段 | |---|---| | 任意卡型 | `title` | | text | `content` | | scene | `description` | | image | `prompt` `aspectRatio` `resolution`(**1k/2k/4k**) `quality`(low/standard/high) `seed` `seedLocked` `style`(`{id,name,prompt}`,null 清空) `camera`(`{body,lens,focalMm,aperture}`,null 清空) | | video | `prompt` `aspectRatio` `resolution`(**480p/720p/1080p/2k/4k**) `durationSec`(0–600] `audio` `mode`(i2v/flf2v) `lastFrameAssetId`(flf2v 尾帧,空串解绑,只能引自己的云端图片资产) `style` `camera`(同图片卡) | | audio · voice | `text` `voiceId` `voiceName` `speed`(0.5–2) `emotion`(9 档) | | audio · sfx | `text` | | audio · music | `musicStyle` `lyrics` `instrumental` | | 其余(audio clone/dialogue、script、group、hub、analyze、director) | **仅 `title`** | `aspectRatio` 两种卡通用:auto / 1:1 / 1:2 / 2:1 / 9:16 / 16:9 / 3:4 / 4:3 / 3:2 / 2:3 / 5:4 / 4:5 / 21:9 / 9:21。**不传就是 16:9**(全站默认画幅)—— 竖屏要用户明确说了才传 `9:16`,别因为「短剧」这个词就自作主张改竖屏。 ⚠️ `resolution` 是**两套词表**:图片卡 1k/2k/4k,视频卡 480p…4k,别混用。 两条行为契约(都是刻意的显式失败,**不再静默**): - 字段不适用于该卡型(含补丁为空)→ 422 `no_applicable_fields`; - 取值不在该卡型词表内或类型不符 → `invalid_field_value` (`aspectRatio` 单独走 `invalid_aspect_ratio`)。 两者的 `message` 都会列出「该卡能改什么 / 该字段合法取值」,照着改即可。 拿不准就先打详读端点看 `params`——**读回来的字段集就是写得进去的字段集**。 ### 生成能力词(十二个) `t2i` 文生图 · `i2i` 图生图/编辑/放大/抠图 · `i2v` 图生视频 · `flf2v` 首尾帧生视频 · `ref2v` 参考图生视频 · `avatar` 数字人口播 · `t2a` 配音 · `music` 音乐 · `sfx` 音效 · `upscale` **视频**高清 · `v2a` 视频抽音轨 · `blockout` 视频转白模。 素材一律用 assetId(64 位小写 hex)引用,不收 URL、不收 base64、不收本地路径。 本地文件先走 `POST /api/v1/assets` 上传换成 assetId(`assets:write`)。 点名了就不降级:请求 `i2v` 却不给 `firstFrame` 得到 400,不会悄悄按文生视频出一段 和分镜图无关的片子。图片放大是 `i2i` + `params.operation:"upscale"`。 做 `t2a` 配音前**先查 `/api/v1/voices` 挑音色**,别不传让上游用默认嗓子—— 那等于放弃角色的声音设定。`kind:"system"` 填 `params.voiceId`, `kind:"user"` 填 `params.voiceName`;id **原样回传不要 trim**。 ### 数字人口播(`avatar`) 一段配音 + 一个形象 → 一条对口型的口播视频。三个专属字段: - `params.drivingAudio`(**必填**)= 驱动配音,**口型与产出时长都由它决定**。 **只收 wav / mp3**:本能力按秒计价,计费时长由服务端从音频字节现测(申报值不可信), 而那台探测器只认这两种容器——m4a / ogg 先转码,否则 400。 - 形象来源**二选一**:`params.firstFrame`(肖像图)或 `params.motionVideo` (参考动作视频)**至少给一个**,都不给 → 400;两个都给走视频驱动版 (帧数由服务端按配音时长自动裁剪,不需要你申报任何时长)。 - **专属数字人(仅管理员)**:`params.digitalHumanId` 填 `GET /api/v1/digital-humans` 返回的 id,形象图与专属模型由服务端从登记记录读取——此时**不传** `firstFrame` / `motionVideo`(同时给 → 400),只支持口播(配 `drivingAudio`),模型选「数字人口播(InfiniteTalk)」。 非管理员的目录恒为空列表,带 id 提交会被拒(403)。 两条容易踩的边界: - **模型面互斥**:数字人模型只出现在 `/api/v1/models?capability=avatar` 里, **不出现在 `i2v` / `flf2v` 的目录里**(它的任务规划恒要求驱动配音, 当普通图生视频用必然失败,且是在冻结积分之后才失败)。拿别的视频模型提 `avatar` → 400 `model_unsupported`。 - **`drivingAudio` / `motionVideo` 只有 `avatar` 消费**:挂在 `i2v` 上直接 400。 那条链路根本不透传这两个字段,放行等于「付了口播的钱、拿回一段没口型的片」。 `durationSec` / `resolution` / `audio` 对本能力**无效**(时长由配音定、无清晰度档、 产物必带配音音轨)。`aspectRatio` 只对「肖像 + 配音」这一版生效,视频驱动版跟随参考视频。 预检报价给个估计的 `durationSec`(≈ 配音秒数)即可,实扣以服务端测得的为准。 ### 视频抽音轨(`v2a`) 一段视频进 → 一条音轨出,**没有生成语义**(服务端 ffmpeg/Demucs/whisper, 同一条视频抽两次结果一致,重试没有意义)。它是角色定嗓流水线的提取环节: `ref2v` 出一条试镜片 → `v2a` 抽人声轨 → 该角色所有对白镜头拿这段样本锁音色。 - `params.video`(**必填**)= 待抽轨源视频 assetId,**必须是视频资产**; - `params.dryVoice` = 干声分离(Demucs)。做定嗓样本**建议开**:试镜片混进环境音/BGM, 锁的就不只是嗓音。分离失败**不失败整单**——产物退回原始音轨,终态元数据 `dryVoiceApplied: false` 如实标注,自己判断要不要重来; - `params.transcribe`(缺省开)= 转写。终态元数据带 `transcript`, **拿它和剧本台词比一比**:念错词的样本别拿去定嗓,换 seed 重出试镜片。 计价按**产出秒**(= 源片秒),时长由服务端从资产字节裁决、不收申报值; 解析不出 → 422 `asset_unresolved`(不猜)。预检给个估计的 `durationSec` 即可。 ### 白模换角:`blockout` + `ref2v` 参考视频 让**新角色复刻一段现成视频的动作与运镜**,两步: 1. `capability:"blockout"` + `params.sourceVideo`(原片 assetId,**必须是视频资产**)→ 一段深度粘土白模视频(几何与动作保留、外观抹掉)。`params.mode` 缺省 `clay_masked` (人物浅灰/背景深灰,只有它能稳定驱动人物动作),`params.keepAudio` 缺省关。 **不消费** `prompt` / `aspectRatio` / `resolution` / `durationSec`(传了不报错也不生效): 画幅、帧率、时长全部跟随源片。确定性变换、无 seed;按产出秒(= 源片秒)计价, 预检给个估计的 `durationSec` 即可。 2. `capability:"ref2v"` + 顶层 `referenceAssetIds`(新角色定妆照)+ 顶层 `referenceVideoAssetIds`(白模片,≤3 段)。**参考视频只提供动作与运镜,参考图只提供长相。** 提示词照这四段写:「`` 是林小满(主角)。人物动作、走位与镜头运动严格跟随 `