API 参考

每一个字段,
都与实现完全一致。

/api/v1 下有三条路由,分别负责提交、轮询与上传。下面写的是接口今天真正执行的约定——没有规划中的参数,也没有取整过的限额。

版本 v1 · 仅限同源调用 · 尚未发放 API key
公开 API 尚未开放。

这些路由只服务本站自己的页面。没有 API key、没有 bearer token、没有 webhook,也没有回调地址;请求以匿名浏览器会话为作用域,从你自己的服务器或其他域名调用不会成功。本页是把现有约定写清楚以便审阅,不是一份可以直接对接的集成文档。

基址与认证

没有密钥,只有会话 Cookie。

路径相对当前站点,不带 Authorization 头。服务端在首次调用时下发 httpOnly 会话 Cookie,并以它界定每个任务的归属。请求体超过 100 KB 会在解析之前被拒绝。

两条可用路由会读取或设置的全部请求头与响应头,以及各自的用途。
请求头方向说明
Content-Type: application/json请求两条 POST 路由都必须携带。请求体解析不出 JSON 会在任何校验之前被拒。
Idempotency-Key请求可选,16–100 个字符,取值范围为 A–Z、a–z、0–9、下划线与连字符。同一个 key 配相同请求体重放会返回原任务;配不同请求体则返回 idempotency_conflict。不带这个头时服务端会自动生成一个,也就等于没有重放保护。
X-Request-Id请求可选的关联 id,最长 64 个字符,只允许字母、数字、点、下划线与连字符。不符合的会被丢弃并重新生成。
Cookie: vv_anon请求首次调用时由服务端下发,保留一年。它标识拥有任务的匿名会话;换一个会话去轮询会得到 task_not_found。它不是账号,也不携带任何余额。
x-request-id响应每个响应都会回带,成功失败都有。反馈问题时请附上它。
Cache-Control: no-store响应所有响应都不可缓存——轮询期间任务状态会在你脚下变化。
Retry-After响应需要等待的秒数,随 429 rate_limited 一起返回,与响应体里的 retryAfterSeconds 对应。
端点

三条路由,其中两条可用

只实现了下表列出的方法。没有列表接口,没有取消接口,也没有回调注册。

每条路由的路径、方法、作用,以及今天是否已经接通。
端点方法作用状态
/api/v1/videosPOST校验请求体、预留匿名额度、提交给视频供应商,并返回供应商任务 id。返回 202 而不是 200——此时视频还不存在。可用 · 同源
/api/v1/videos/{taskId}GET返回任务的当前状态,且只对创建它的会话返回。持续轮询直到状态变成 completed 或 failed。可用 · 同源
/api/v1/uploadsPOST对象存储尚未开通,这条路由固定返回 503 not_configured,reason 为 upload_storage。不接收任何字节,也不做任何转发。正因如此,创建接口同样会拒绝任何带 url 的附件条目。尚未接通
创建生成任务

请求体就是一份 composer 状态

要生成什么、用哪个模型、多大尺寸。服务端会重跑浏览器里那套校验,面板拒绝的参数在这里同样会被拒绝。

POST /api/v1/videos 请求体的顶层字段、类型与是否必填。
字段类型是否必填说明
promptstring必填画面描述。哪怕内容为空,这个键也必须存在且为字符串;服务端会做 trim,长度上限 2,000 个字符。空提示词是否允许取决于当前模式。
model"vivid-1" | "seedance-2-5" | "kling-3" | "veo-3-1" | "minimax-h3-max" | "wan-3" | "kling-motion"必填模型 key。传未知的 key 会立刻失败;kling-motion 边界层接受,但面板里不提供。旧版的数字索引写法仍然兼容。
mode"text" | "image" | "reference" | "extend" | "edit" | "motion"必填必须是所选模型真正支持的模式,否则在预留任何额度之前就会被拒。
resolution"480P" | "720P" | "768P" | "1080P" | "4K"必填必须是该模型列出的取值,严格匹配,P 为大写。
aspect"auto" | "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | null必填只有在该模式会按输入自适应画幅时才允许传 null,此时服务端会替换成 16:9;否则必须是列出的取值之一。
durationnumber | "auto" | null必填整数秒,且落在模型允许的区间内。只有模型允许时才能用 "auto",匿名调用一律不允许。只有不提供时长控制的模型才接受 null,服务端会替换成 5。
audioboolean必填必填布尔值,即使模型没有音频开关也要传。音频影响单价时,费用估算会随之变化。
mediaobject[]可选附件引用,最多 50 条,纯文本模式下会被整体忽略。每条需要 id、kind、role、url、name 与 bytes,可以再带 mime、width、height 与 seconds;role 取值为 ref, start, end, motion, sound, source, character。由于上传尚未接通,任何 url 不为 null 的条目都会被拒:未配置上传域时,边界校验直接以 400 validation_failed 拒绝;只有在上传域已配置、而对象存储仍未接通时,才会返回 503 not_configured。
advancedobject可选供应商侧的高级参数,白名单为 watermark, watermarkText, webSearch, seed, quality, translate, orientation。取值只能是字符串、数字或布尔值,出现任何其他键都会导致整个请求被拒。
sourceobject | null按需对上一个任务的续作:一个 token 加一个 kind,kind 为 "lastFrame" 或 "extend"。token 是任务完成时轮询接口签发的签名声明——裸的供应商任务 id 一律不收。lastFrame 需要 image 模式,extend 需要 Veo 3.1 的 extend 模式,同时服务端必须配置好续作签名密钥。
cURL · POST /api/v1/videos
# The response cookie scopes the task to this anonymous session.
curl -X POST https://vgent.com/api/v1/videos \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  --cookie-jar vgent.cookies \
  -d '{"model":"vivid-1","mode":"text","prompt":"Neon koi swimming through a flooded Tokyo alley","resolution":"720P","aspect":"16:9","duration":5,"audio":true,"media":[],"advanced":{},"source":null}'

# Poll with the same cookie; any other session gets task_not_found.
curl https://vgent.com/api/v1/videos/b7f3c0a4-51d2-4c8e-9a17-2f6d0e5b3c91 --cookie vgent.cookies
TypeScript · POST /api/v1/videos
const created = await fetch('/api/v1/videos', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    model: 'vivid-1',
    mode: 'text',
    prompt: 'Neon koi swimming through a flooded Tokyo alley',
    resolution: '720P',
    aspect: '16:9',
    duration: 5,
    audio: true,
    media: [],
    advanced: {},
    source: null,
  }),
});

const { data, error } = await created.json();
if (error) throw new Error(`${error.code}: ${error.message}`);

// 202 Accepted returns { taskId, model }; the video does not exist yet.
let task;
do {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  const polled = await fetch(`/api/v1/videos/${data.taskId}`, { cache: 'no-store' });
  task = (await polled.json()).data;
} while (task.status === 'pending' || task.status === 'processing');

// A failed generation still arrives as HTTP 200 with task.error set.
console.log(task.status === 'completed' ? task.videoUrl : task.error);
查询任务

所有状态共用一种结构

每个阶段返回的都是同一个对象,字段随任务推进逐步填上。工作台每 3 秒轮询一次,超过 20 分钟放弃。

GET /api/v1/videos/{taskId} 返回的任务对象字段,按声明顺序排列。
字段类型说明
taskIdstring供应商任务 id:8 到 64 个字符,只含字母、数字与连字符。格式不对会在调用供应商之前就被拒。
status"pending" | "processing" | "completed" | "failed"pending 与 processing 是非终态,completed 与 failed 是终态。到终态就停止轮询。
modelstring任务真正运行时使用的供应商模型 id,例如 doubao-seedance-2.0——不是你传进来的模型 key。
videoUrlstring | null只有状态变成 completed 后才有值。它指向供应商托管的产物,会过期,需要的内容请尽快取走。
creditsConsumednumber | null任务实际消耗的供应商积分,由供应商上报。它不是界面上显示的 Vgent 积分;后者是这个数字乘以 0.75 再向上取整。
providerCreditsnumber | null同一个供应商数值的另一个显式命名。两个字段都是供应商口径,不是两种货币。
lastFrameUrlstring | null尾帧地址,前提是模型返回了尾帧且任务已完成。lastFrame 续作就是从它接着往下生成的。
ratiostring | null供应商上报的输出画幅,若它有上报。
resolutionstring | null供应商标注的输出分辨率,可能是小写,也可能与你请求的不一致。
durationnumber | null供应商测得的输出时长(秒),可能与请求的时长有出入。
outputFormatstring | null供应商产出的封装格式,例如 mp4。
sourceTokenstring | null签名续作 token。只有任务完成、且服务端持有签名密钥时才会出现;完成后 24 小时过期。
progressnumber | null供应商上报进度时为 0 到 100,会被夹在这个区间内。null 表示供应商没有给出进度。
estimatedSecondsnumber | null供应商自己对剩余耗时的估计,若它有提供。
createdAtnumberUnix 秒。
completedAtnumber | nullUnix 秒;供应商未上报完成时间之前为 null。
errorobject | null生成失败时由供应商给出的 code 与 message。它解释的是生成本身而不是这次 HTTP 调用:任务失败时 HTTP 依然是 200。
200 OK · GET /api/v1/videos/{taskId}
{
  "data": {
    "taskId": "b7f3c0a4-51d2-4c8e-9a17-2f6d0e5b3c91",
    "status": "completed",
    "model": "doubao-seedance-2.0",
    "videoUrl": "https://cdn.aivideoapi.ai/outputs/b7f3c0a4.mp4",
    "creditsConsumed": 190,
    "providerCredits": 190,
    "lastFrameUrl": "https://cdn.aivideoapi.ai/outputs/b7f3c0a4-last.jpg",
    "ratio": "16:9",
    "resolution": "720p",
    "duration": 5,
    "outputFormat": "mp4",
    "sourceToken": "eyJ0YXNrSWQiOiJiN2YzYzBhNCIsImV4cCI6MTc4OTE3MTgxMn0.dGFza190b2tlbl9zaWduYXR1cmU",
    "progress": 100,
    "estimatedSeconds": 0,
    "createdAt": 1789084800,
    "completedAt": 1789085412,
    "error": null
  },
  "error": null
}
响应信封

成功失败共用一个信封

成功时数据放在 data,error 为 null;失败时反过来:data 为 null,error 里是一个稳定的机器可读 code。每个响应还会带上请求 id,并标记为 no-store。

判断请用 code,不要用 message

错误 message 是写给日志看的英文句子。工作台按 code 映射到自己的多语言文案,从不把原始 message 展示给访客。基于这些接口做开发也应该照此处理。

error 对象内部的字段,只有 data 为 null 时才出现。
字段类型说明
codestring稳定标识,完整清单见下表。真正值得据以分支的就是它。
messagestring英文、面向日志,可能随时调整,不属于约定的一部分。
reasonstring?部分失败会带上,用来指出是哪一部分出了问题:哪个依赖没配置、续作 token 因何被拒,或者触发了哪条校验规则。
retryAfterSecondsnumber?只在 429 rate_limited 时出现,与 Retry-After 响应头一致。
202 Accepted · POST /api/v1/videos
{
  "data": {
    "taskId": "b7f3c0a4-51d2-4c8e-9a17-2f6d0e5b3c91",
    "model": "doubao-seedance-2.0"
  },
  "error": null
}
429 Too Many Requests · POST /api/v1/videos
{
  "data": null,
  "error": {
    "code": "rate_limited",
    "message": "Too many requests.",
    "retryAfterSeconds": 47
  }
}
状态码

每个状态码在这里的含义

其中两个含义比通常更重要:创建返回的是 202 而不是 200;生成失败时依然返回 200,失败信息放在 data 里。

两条可用路由可能产生的全部 HTTP 状态码,按升序排列。
状态码含义
200任务读取成功。这也包括生成失败的任务,所以取用产物前请先看 data.status。
202任务已被接受并提交给供应商,响应体是任务 id 与供应商模型。重放同一个 Idempotency-Key 会再次返回原来的 202。
400请求体没通过边界校验、任务 id 格式不对、同一个幂等 key 配了不同参数,或者续作 token 被拒。
402为本站供资的供应商账户积分耗尽或触及消费上限。没有向你收取任何费用。
403请求本身合法,但超过了匿名单条 300 供应商积分的上限,或使用了匿名调用不允许的附件。
404该 id 的任务不属于你的会话,或者供应商没有这条记录。
409同一个 Idempotency-Key 的上一次请求已经打到供应商,但始终没有对账成功,因此没有任务 id 可以返回。等那次提交被记录后即会恢复;换新 key 意味着再付一次生成费用。
429十分钟滚动窗口内提交次数过多,或视频供应商正在对本站限流。只有前一种情况会带 Retry-After 与 retryAfterSeconds。
500未处理的服务端错误。不会暴露任何内部细节;请求 id 在响应头和日志里都有。
502供应商有响应,但内容不可用:格式错误的载荷、被拒的凭证,或映射不到更具体分类的错误。
503有依赖未配置——供应商密钥、上传、续作签名或持久化限流存储——也可能是共享的当日额度已经用完。
504供应商在 30 秒内没有响应,或者完全无法连通。
错误码

接口会返回的全部错误码

下面就是 error.code 里会出现的确切字符串。这个清单是封闭的:出现别的值就是 bug。

按各自返回的 HTTP 状态码归组,从 400 排到 504。
错误码HTTP触发场景
validation_failed400某个字段没通过校验。reason 会指出触发的规则,例如缺少提示词、时长超出区间,或所选模型不支持该画幅。
invalid_request400URL 里的任务 id 不是 8 到 64 个字母、数字与连字符,或者供应商认为提交本身不合法。
idempotency_conflict400同一个 Idempotency-Key 配了不同的请求体。换新参数就换新 key。
source_invalid400续作 token 格式错误、已过期、签给了别的会话或模型,或者指向的任务已不再符合条件。reason 区分具体情形。
insufficient_credits402为本站供资的供应商账户已无积分。与你这条请求无关。
spend_limit_exceeded402本站配置的供应商消费上限已经触顶。
free_limit_exceeded403这条请求的费用超过匿名单条上限,或附带了匿名调用不能发送的素材。自动时长也会落到这里,因为它的费用无法事先封顶。
task_not_found404任务不属于你的会话,或者供应商没有这条记录。两种情形被刻意做成无法区分。
submission_pending409使用这个幂等 key 的上一次提交已经打到供应商,但响应丢失了,因此记录被刻意保留且没有任务 id。用同一个 key 重试仍会得到这个错误,而不会重复扣费。
rate_limited429匿名窗口按客户端地址限制为每十分钟 5 次提交。retryAfterSeconds 会告诉你要等多久。
provider_rate_limited429是视频供应商在对本站限流,而不是在限你。稍后重试即可。
internal_error500接口内部出现意料之外的失败。请附上响应头里的请求 id。
provider_error502供应商返回了映射不到更具体分类的错误。
provider_bad_response502供应商返回了非 JSON 响应、没有任务 id,或任务载荷缺少 id / status。
provider_auth502供应商拒绝了本服务器的凭证或来源地址。这是我们这边的配置问题,不是你的问题。
not_configured503有依赖缺失。reason 区分上传、续作签名、持久化限流存储与供应商密钥。
free_budget_exhausted503匿名生成的共享当日额度已用完,跨过 UTC 日界后重置。
model_offline503供应商当前不提供所请求的模型。
provider_unreachable504完全连不上供应商。
provider_timeout504供应商在 30 秒内没有响应。上游可能已经建好了任务,因此幂等记录会被保留而不是释放,用同一个 key 重试会得到 submission_pending。
限流与免费额度

匿名调用能拿到什么

目前还没有账号体系,所有调用都是匿名的,共用同一套限额。接口内部按供应商积分计费,只在展示时才做换算。

每一项匿名限制的当前取值,以及它的计量单位。
限制项取值说明
免费额度200 Vgent credits面向新访客展示的额度,单位是 Vgent 积分。它是展示口径,服务端并不为你保存余额。
单条上限300供应商积分。估算超过这个数的请求会在提交之前以 free_limit_exceeded 被拒。
每 10 分钟提交数5按客户端地址计算的滚动窗口。超出会返回 rate_limited,并带上 Retry-After。
共享当日预算20,000所有匿名调用共享的供应商积分,按 UTC 日计。用完后提交会返回 free_budget_exhausted,直到跨日重置。部署时可以配置成别的数值。
单次图片数2匿名调用最多能附带这么多张图片。
视频与音频附件0匿名调用完全不能附带视频或音频,出现一条就会拒绝整个请求。
提示词长度2,000字符数,当前列出的所有模型都是这个值。请求会按所选模型自己的上限来校验。
请求体大小100 KB字节数,在解析之前就会测量。超过的请求根本不会被读取。
幂等记录24 h幂等 key、请求指纹与任务归属会被记住这么久,重放才能拿回原来的任务。
积分换算× 0.75Vgent 积分 = 供应商数值乘以它再向上取整。
生产环境需要持久化存储

限流、当日预算与幂等记录都存放在兼容 Redis 的 REST 存储里。没有它,生产部署会直接拒绝付费提交;开发模式会退回进程内存,重启后所有记录都会丢失。

现在开始,完全免费

200 积分免费额度方案。当前可匿名创作,无需信用卡。

打开工作台