Seedance 视频 API
基址 https://__ORIGIN__。创建任务后轮询状态,再下载 MP4;纯文本即可生成,参考图可选。
字字动画专用接入指南
视频插件配置、模型参数、参考图和任务恢复,按下面的顺序接入。
下载本站通用视频插件,安装后填写地址和 API Key。插件自动读取分镜提示词与参考图片,完成创建、查询和 MP4 下载,支持断网后恢复原任务。
适用于支持 Python 自定义视频插件的字字动画版本。请在视频模型管理器中选择“Seedance 聚合 · 通用视频”。推荐安装包 1.4.2,修复原画下载校验,文件请求或传输中断会自动重试下载,不重新生成。继续支持同镜头批量并发,各条视频独立保存与恢复。模型与时长均为下拉选择。
解压后安装到字字动画的视频插件目录;无需单独安装 Python 依赖。
1. 安装与配置
- 在本站账户控制台的 API Key 页面创建并保存 Key,使用
sk-开头的值。登录密码、登录令牌和充值卡密不能代替它。 - 退出字字动画,下载并解压 ZIP,将整个
video_plugin_seedance文件夹放入安装目录的_internal/plugins/video_plugins/。最终应能找到_internal/plugins/video_plugins/video_plugin_seedance/main.py;不要多嵌套一层同名文件夹。 - 重启字字动画,在视频模型管理器 → 自定义插件中选择 Seedance 聚合 · 通用视频。菜单名可能随版本变化,设置页面依赖字字动画自带的 PluginSDK。
- 本站地址已默认填好,粘贴 API Key 即可。配置页会自动获取模型与时长,直接下拉选择,无需手写;切换模型后时长自动联动。首次选 Seedance 2.0 Fast → 5 秒,分辨率默认 720p;先用纯文本、数量 1,确认后再加参考图和批量任务。
- 通过带 Key 的
GET /v1/pricing确认本人价格,通过GET /v1/balance确认余额。批量任务按实际任务数分别计费,具体以账户价格和账单为准。
https://__ORIGIN__https://__ORIGIN__/v1/videosAPI Key 输入框只填写 Key 本身;插件发送请求时使用 Authorization: Bearer sk-your-key,JSON 请求使用 Content-Type: application/json。避免拼出重复的 /v1/v1/videos。本站使用 /v1/videos,不是 /v1/chat/completions。
模型列表通过 GET /v1/catalog 自动获取,也可点击插件中的重新获取模型与时长;读取目录不扣费。获取失败时保留当前选项,刷新不会擅自改变已选参数。升级至 1.4.2 时先退出字字动画,覆盖安装包中的 main.py、ui/index.html 和 README.md 三个文件,保留原配置和 .seedance-tasks 任务记录,重启后确认配置页显示 1.4.2。
不满意或失败后想重新生成:使用“提交生成”模式,成功下载完成或确认失败后直接再次点击当前镜头的“重新生成”即可,无需改提示词或生成版本;新一版按本人价格计费,旧视频和失败记录保留。同一批量位置、相同参数在处理中或下载中断时仍会恢复原任务。
同镜头批量并发:每镜头可设置多条,两个镜头各 3 条对应 6 个独立任务,按本人价格逐条计费。插件已移除同镜头并发限制,同时执行数量由字字的插件任务设置控制。各条视频单独保存和恢复,请保留原配置及 .seedance-tasks。
网站已成功,只补下载原画:选择“仅恢复/下载原视频”,保持原分镜、提示词、图片、模型参数、生成版本、API Key 及素材目录不变再次执行,不会新建一版或重复扣费;不要增加生成版本或点击“准备新版本”。原画保留生成结果的原始码率,当前分辨率仍为 720p,不做放大或二次转码。
2. 字字动画参数与本站字段
| 插件设置 / 分镜素材 | 本站字段 | 填写规则 |
|---|---|---|
| 视频提示词 | prompt | 必填,1–6000 字符;使用当前分镜的视频提示词。 |
| 模型 | model | seedance2.0 / seedance2.0fast:5、10、15 秒;seedance2.0mini:5、10 秒;seedance2.5:30 秒。 |
| 时长 / 画面比例 | duration / ratio | 时长传整数秒,不能传“15s”;竖屏用 9:16,横屏用 16:9,完整范围见模型与参数。 |
| 分辨率 | resolution | 当前为 720p。 |
| 本地参考图片 | image_ids | 先逐张上传,再提交返回的图片 ID 数组。2.5 最多 30 张,其他模型最多 9 张。 |
| 公开图片地址 | image_urls | 直接返回图片的 HTTP(S) 地址;不能传本地路径、素材网页地址或需要登录的图片链接。 |
| 首尾帧 / 参考视频 | 无专用字段 | /v1/videos 没有专用首尾帧或参考视频字段;牛来系列的素材接入见全能参考。 |
| 参考音频 | audio_b64s | 可选,按连接顺序提交最多 3 段;支持 MP3、M4A、WAV、OGG、FLAC,单段不超过 50 MiB,合计不超过 60 MiB;也兼容单段字段 audio_b64。 |
3. 自定义视频插件请求协议
- 上传图片(可选)。
POST /v1/files发送{"image_b64":"图片的 Base64"},读取响应的image_id。使用 JSON,不是 multipart;PNG、JPEG、WebP 单张解码后最多 50 MiB,上传图片有效期一小时。外部图片地址单张最多 25 MiB。详见上传参考图。 - 创建任务。为每个新生成保存独立操作编号和完整请求体,在
Idempotency-Key请求头中传入该编号,然后POST /v1/videos。下面是 15 秒竖屏文生视频的请求体;有参考图时再加入image_ids。
{
"prompt": "雨后的城市街道,镜头缓慢向前移动,柔和电影光影",
"model": "seedance2.0fast",
"duration": 15,
"ratio": "9:16",
"resolution": "720p"
}
- 保存任务 ID 并轮询。创建返回 HTTP
202,从根字段id读取任务编号,不是task_id或data.id。随后GET /v1/videos/{id};queued、running表示继续等待,succeeded才能下载,failed为本次任务失败。不要把排队或受理当作生成成功。 - 下载原画到字字动画素材目录。成功后用同一个 Key 调用
GET /v1/videos/{id}/signed_url?download=1,确认响应quality为original,再原样使用返回的本站url下载,无需附加 Key。链接中的签名、质量与文件标识必须保留,文件响应应为X-Video-Quality: original。普通/file、不带download=1的签名请求以及任务状态中的file/download_url是轻量预览链路。原画暂未就绪时保留已有任务,稍后重新获取原画链接;不要改用预览文件或重新提交生成。插件完整保存 MP4 后才向字字动画报告成功。
4. 批量、超时和积分恢复
- 支持同镜头批量并发,数量由字字拆分提交,各条独立计费。同一批量位置、相同参数的未完成任务继续恢复原操作;不同位置或改变参数、版本可并行新建。默认“提交生成”模式下,成功下载完成后再次提交会生成新一版;仅补下载时选仅恢复/下载原视频。
- 超时后保持原参数和生成版本,选择“仅恢复/下载原视频”后再次执行。保留作品素材目录中的
.seedance-tasks、原参考图、原地址及原 Key;更换内容、版本或清理任务记录会影响原任务恢复。 - 建议单次 API 请求等待 60 秒、图片上传 90 秒,任务查询间隔约 5 秒;任务整体等待与单次 HTTP 超时分开处理。插件等待结束不代表服务端任务已取消。
- 提交超时、断网、空响应或
null时,保留原 Key、操作编号和请求体,用同一Idempotency-Key恢复;收到id后只查询该任务。不要因不确定的响应而重新生成操作编号。 - 确认
failed后,如决定重做,保持“提交生成”模式,直接再次点击“重新生成”,无需增加生成版本。刚查到失败的本次执行会停止,不会自动连续下单。失败任务的退款金额和到账情况以本站账单为准,不能仅根据插件显示“失败”推断全额退款。
5. 常见接入问题
- 插件不显示 / 配置页未加载:确认上述目录层级,重启字字动画,并确认版本支持 Python 自定义视频插件。已验证公开插件接口、模拟配置页面和隔离 API 故障恢复;各版本客户端界面仍可能存在差异。
- 401:检查 Key 是否有效,以及请求头是否为
Authorization: Bearer …。 - 404 / 返回 HTML:核对基址和路径,确认调用了本站视频接口,保留 HTTP 状态和请求编号便于排查。
- 400 / 422:核对模型对应的时长、整数类型和图片字段;
seedance2.0mini不接受 15 秒。 - 402:查询本人余额;若原提交结果不明,先恢复原操作,再决定是否充值或创建新任务。
- 429:根据响应的
error.type区分并发或每日限制,按Retry-After(如有)等待;409 idempotency_in_progress继续恢复原操作。 - 本站成功、字字动画没有素材或提示“未收到已确认的原画文件”:先升级至 1.4.2,再选择“仅恢复/下载原视频”,保留原参数、生成版本、配置和
.seedance-tasks补下载。不要增加版本重新付费生成;若仍失败,保留提示中的插件版本和任务编号便于排查。
开始接入
从账户控制台的 API Key 页面创建并复制 sk- 开头的 Key,服务端通过请求头 Authorization: Bearer sk-your-key 传入。登录令牌、密码和充值码不能代替 API Key。
先查询余额确认鉴权,再用 Python 完整示例 跑一次纯文本生成。余额查询不扣积分,创建视频任务会按当前价格扣积分。Key 保存在服务端环境变量中。
$env:VIDEO_API_BASE = "https://__ORIGIN__"
$env:VIDEO_API_KEY = "sk-your-key"
$env:VIDEO_STATE = "order-001.json"
curl.exe --fail-with-body -sS --max-time 90 "$env:VIDEO_API_BASE/v1/balance" -H "Authorization: Bearer $env:VIDEO_API_KEY" -A "vinted-video-client/1.0"
# 将下方完整示例保存为 video_client.py 后运行(Python 3.10+)
python video_client.py
export VIDEO_API_BASE="https://__ORIGIN__"
export VIDEO_API_KEY="sk-your-key"
export VIDEO_STATE="order-001.json"
curl --fail-with-body -sS --max-time 90 "$VIDEO_API_BASE/v1/balance" -H "Authorization: Bearer $VIDEO_API_KEY" -A "vinted-video-client/1.0"
# 将下方完整示例保存为 video_client.py 后运行(Python 3.10+)
python3 video_client.py
同一订单重试保持 VIDEO_STATE 不变;新订单换一个文件名,例如 order-002.json。状态文件保存操作编号、参数和任务 id,不保存 Key。每个订单同一时间只运行一个进程。
下方分步骤的 curl 示例适用于 Bash,需 curl 7.76+、jq、Python 3;PowerShell 可直接使用上面的环境设置与完整 Python 示例。基址只填域名根,不重复添加 /v1。
| 方法与路径 | 用途 | 鉴权 |
|---|---|---|
GET /health · GET /ready | 服务存活 / 就绪探针 | 否 |
GET /v1/catalog | 模型、合法时长、比例、分辨率 | 否 |
GET /v1/pricing | 不带 Key 为公开价;带 Key 为该账户适用售价 | 可选 |
GET /v1/balance | 积分余额与最近流水 | 是 |
POST /v1/files | 上传参考图,换取 image_id | 是 |
GET /v1/files/{image_id} | 读取已上传的参考图 | 是 |
POST /v1/videos | 创建视频任务(返回 202 + 任务 id) | 是 |
GET /v1/videos/{job_id} | 查询任务状态 | 是 |
GET /v1/videos/{job_id}/signed_url | 获取轻量版链接;加 ?download=1 获取原片下载链接 | 是 |
GET · HEAD /v1/videos/{job_id}/file | 下载 / 预览视频(支持 Range) | Key 或有效签名 |
GET /v1/image-ai/settings | 彩绘图片单价与保留时间 | 是 |
POST /v1/image-ai/submit | 人物照片转彩绘,批量逐张受理 | 是 |
GET /v1/image-ai/batch/{batch_id} | 查询图片批次 | 是 |
GET /v1/image-ai/jobs/{job_id} | 查询图片状态与下载地址 | 是 |
GET /v1/image-ai/jobs/{job_id}/file | 下载彩绘图片 | Key 或有效签名 |
Python 完整示例
Python 3.10+,无需安装第三方包。先设置上方环境变量,再下载 video_client.py 运行。默认生成 5 秒竖屏视频,结果保存为 out.mp4。
图片可选:Windows 设置 $env:VIDEO_IMAGE = "reference.png";macOS / Linux 设置 export VIDEO_IMAGE="reference.png"。仅在新订单首次运行时读取图片和参数;已有状态文件继续原订单。需要修改提示词时,先修改代码中的请求体并为新订单更换状态文件名。
video_client.py
import base64, hashlib, http.client, json, os, shutil, time, uuid
import urllib.error, urllib.request
from pathlib import Path
from urllib.parse import urljoin
base = os.environ.get("VIDEO_API_BASE", "https://__ORIGIN__").rstrip("/")
key = os.environ.get("VIDEO_API_KEY", "").strip()
state_path = Path(os.environ.get("VIDEO_STATE", "order-001.json"))
output = Path(os.environ.get("VIDEO_OUTPUT", "out.mp4"))
agent = "vinted-video-client/1.0"
if not key.startswith("sk-") or key == "sk-your-key":
raise SystemExit("Set VIDEO_API_KEY to your own sk- key first.")
class ApiError(SystemExit):
def __init__(self, status, message):
super().__init__(message)
self.status = status
def save_state():
temporary = state_path.with_suffix(state_path.suffix + ".tmp")
temporary.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
temporary.replace(state_path)
def call(method, path, body=None, idem=None, field=None):
data = None if body is None else json.dumps(body).encode("utf-8")
headers = {"Authorization": "Bearer " + key, "User-Agent": agent}
if data is not None:
headers["Content-Type"] = "application/json"
if idem:
headers["Idempotency-Key"] = idem
for attempt in range(3):
delay = 5 * (attempt + 1)
req = urllib.request.Request(base + path, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=90) as response:
result = json.load(response)
if not isinstance(result, dict) or (field and
(not isinstance(result.get(field), str) or not result[field])):
raise ValueError("empty or incomplete JSON response")
return result
except urllib.error.HTTPError as exc:
raw = exc.read().decode("utf-8", "replace")
try:
detail = json.loads(raw)
except ValueError:
detail = {}
error = detail.get("error", {}) if isinstance(detail, dict) else {}
if not isinstance(error, dict):
error = {}
message = "HTTP %s: %s; request_id=%s" % (
exc.code, error.get("message", raw[:300]),
error.get("request_id", exc.headers.get("X-Request-ID", "")))
retryable = (exc.code in {408, 429, 500, 502, 503, 504} or
(exc.code == 409 and error.get("type") in
{"idempotency_in_progress", "submission_reclaimed"}))
if not retryable:
raise ApiError(exc.code, message + "\nState kept: " + str(state_path))
retry = exc.headers.get("Retry-After")
if retry is None and isinstance(detail, dict):
retry = detail.get("retry_after")
try:
delay = max(delay, float(retry))
except (TypeError, ValueError):
pass
except (OSError, ValueError, http.client.HTTPException) as exc:
message = str(exc)
if attempt == 2 or delay > 60:
raise SystemExit(message + "\nWait and rerun with the SAME VIDEO_STATE and API Key: " + str(state_path))
time.sleep(delay)
# Persist the operation before the first creation request; never persist the key.
key_hash = hashlib.sha256(key.encode()).hexdigest()
if state_path.exists():
state = json.loads(state_path.read_text(encoding="utf-8"))
if state.get("base") != base or state.get("key_hash") != key_hash:
raise SystemExit("This state belongs to a different base URL or API Key.")
else:
state = {"base": base, "key_hash": key_hash, "operation_id": uuid.uuid4().hex,
"image_path": os.environ.get("VIDEO_IMAGE", ""),
"body": {"prompt": "A clean product showcase", "model": "seedance2.0fast",
"duration": 5, "ratio": "9:16"}}
save_state()
def upload_reference():
image = Path(state["image_path"]).read_bytes()
digest = hashlib.sha256(image).hexdigest()
if state.get("image_hash", digest) != digest:
raise SystemExit("The reference image changed. Restore the original image to resume.")
uploaded = call("POST", "/v1/files", {
"image_b64": base64.b64encode(image).decode("ascii")
}, field="image_id")
state.update(image_hash=digest, image_expires_at=uploaded["expires_at"])
state["body"]["image_ids"] = [uploaded["image_id"]]
save_state()
if not state.get("job_id"):
if state["image_path"] and not state["body"].get("image_ids"):
upload_reference()
try:
job = call("POST", "/v1/videos", state["body"],
idem=state["operation_id"], field="id")
except ApiError as exc:
# Reconcile the original submission first. Only refresh an expired
# upload after an explicit rejection, retaining the same operation.
if (exc.status != 400 or not state["image_path"] or
state.get("image_expires_at", float("inf")) > time.time()):
raise
upload_reference()
job = call("POST", "/v1/videos", state["body"],
idem=state["operation_id"], field="id")
state["job_id"] = job["id"]
save_state()
job_path = "/v1/videos/" + state["job_id"]
print("job_id=" + state["job_id"], flush=True)
deadline = time.monotonic() + 1800
while time.monotonic() < deadline:
current = call("GET", job_path, field="status")
if current["status"] in {"succeeded", "failed"}:
break
if current["status"] not in {"queued", "running"}:
raise SystemExit("Unexpected status: " + str(current["status"]))
time.sleep(5)
else:
raise SystemExit("Still pending; rerun with the SAME VIDEO_STATE to continue polling.")
if current["status"] != "succeeded":
raise SystemExit("Generation failed: " + json.dumps(current, ensure_ascii=False))
# A signed URL needs no Authorization header and expires; mint it when needed.
signed = call("GET", job_path + "/signed_url?download=1", field="url")
url = signed["url"]
download = urljoin(base.rstrip("/") + "/", url)
part = output.with_name(output.name + ".part")
request = urllib.request.Request(download, headers={"User-Agent": agent}, method="GET")
with urllib.request.urlopen(request, timeout=600) as response, part.open("wb") as handle:
if response.status != 200:
raise SystemExit("download failed: %d" % response.status)
shutil.copyfileobj(response, handle)
expected = response.headers.get("Content-Length")
if expected is not None and handle.tell() != int(expected):
raise SystemExit("Incomplete download; rerun with the SAME VIDEO_STATE.")
part.replace(output)
print("saved " + str(output))
请求超时、空响应、临时错误最多自动重试 3 次;等待过久或需要充值时会退出并保留状态。重新运行可继续提交确认、轮询或下载,不会因为本地等待结束而新建订单。视频失败时以任务返回的 error 和实际账单为准。
接入须知
使用自定义视频 REST 接口,接入工具需支持自定义路径、JSON 请求体与异步轮询。
- 基址
https://__ORIGIN__;业务接口都在/v1下。不要直连源站 IP。 - 鉴权 每个受保护请求带请求头
Authorization: Bearer sk-your-key(Bearer后有一个空格)。 - 格式 JSON 请求使用
Content-Type: application/json;图片与视频读取返回二进制。视频请求发往POST /v1/videos,不能直接套用聊天接口或图片生成接口。 - 异步
POST /v1/videos成功返回 HTTP 202 和任务id,不会等到视频生成完。必须轮询GET /v1/videos/{job_id}直到succeeded或failed。把 202 当失败是最常见的错。 - 超时 客户端读超时设 ≥ 60 秒;轮询间隔 3–5 秒;总等待预算 ≥ 10 分钟(
seedance2.5/30 秒更久)。不要用一次同步请求等成品,也不要套 30 秒短超时。 - 幂等 创建前保存唯一
Idempotency-Key与请求体;超时、空响应或重启后,使用同一个 API Key、操作编号和请求体恢复。新订单用新编号。收到任务id后只需查询;客户端等待超时不代表任务已取消。 - 计费 按视频计积分;余额不足返回
402;触发频率/额度限制返回429(按Retry-After等待)。 - 下载 使用
GET /v1/videos/{job_id}/signed_url?download=1获取原片下载链接,普通签名和列表中的download_url是轻量版。母版获取失败时返回固定的轻量版链接,以响应的quality(original或preview)为准。只用本站返回的地址,支持Range断点续传;接口选好版本后,续传不会跨版本。不要缓存或转存这些短期地址。
模型与参数
以 GET /v1/catalog 实时返回为准;下表为当前能力。所有模型都支持图生视频,分辨率均为 720p。
| model | 合法 duration(秒) | 参考图上限 |
|---|---|---|
seedance2.5 | 30 | 30 张 |
seedance2.0 | 5 / 10 / 15 | 9 张 |
seedance2.0fast | 5 / 10 / 15 | 9 张 |
seedance2.0mini | 5 / 10 | 9 张 |
比例 ratio:1:1、3:4、4:3、9:16、16:9、21:9。不传或传空字符串时使用模型默认比例,需要竖屏请明确传 9:16。运镜 camera_movement:空字符串(模型默认)、auto 或 fixed。分辨率 resolution:720p。提示词 prompt 最长 6000 字符。
创建前查询带 Key 的 GET /v1/pricing,确认模型和时长有可用价格。目录价格是公开价,可能与当前账户价格不同。
- 基址必须是
https://__ORIGIN__,不要打源站 IP。 - 鉴权头必须是
Authorization: Bearer sk-...,Bearer后有空格;不要只传 Key,也不要用X-API-Key。 POST /v1/videos成功是 HTTP 202,例如{"id":"f84c6a1e9b234f6e8a94231450000001","status":"queued"}。幂等重放可能返回running或终态。model只能用GET /v1/catalog里的 id:seedance2.5/seedance2.0/seedance2.0fast/seedance2.0mini。推荐用这四个展示 id。若你此前对接时使用的是带下划线的旧写法,服务端仍然兼容、计价与时长规则完全一致,不必改造;新接入请统一用上面的展示 id。- 2.5 只能
duration: 30;2.0 / 2.0fast 为 5/10/15;mini 为 5/10。 - 图生视频先
POST /v1/files拿image_id再传image_ids;也可直接传公网图床直链image_url(单张)或image_urls(多张),两种方式可混用并合计张数(seedance2.5最多 30 张,其余模型最多 9 张)。公开文档不走 multipart。 - 轮询时内部
polling对外显示为running。直到succeeded或failed。 signed_url返回url、expires_at和quality。不带download=1时获取轻量版预览链接;下载原片时重新请求GET /v1/videos/{job_id}/signed_url?download=1,不要直接改写旧预览链接。返回的url可能是相对路径或绝对地址,使用 URL 解析函数处理,避免重复拼接域名,并保留签名中的版本参数。签名有效期为 600 秒,以返回的expires_at为准。该接口未就绪返回 409;直接访问/file未就绪返回 404。
余额查询
GET /v1/balance 返回当前积分余额与最近流水。响应字段为 balance(整数积分)与 ledger(最近流水数组)。
BASE="${VIDEO_API_BASE:-https://__ORIGIN__}"
KEY="${VIDEO_API_KEY:?Set VIDEO_API_KEY to the original sk- key}"
curl --fail-with-body -sS --max-time 90 "$BASE/v1/balance" \
-H "Authorization: Bearer $KEY" -A "vinted-video-client/1.0"
# {"balance":12345,"ledger":[{"reason":"consume","delta":-20,"job_id":"example-job","balance_after":12345,"created_at":1756000000}]}
流水字段是 delta,负数为扣款、正数为入账。普通失败全额退款;适用内容审核扣费时可能部分退款或退款为零。退款结算可能晚于任务终态,以该任务的账单记录为准。
上传参考图
图生视频可选。POST /v1/files 每次上传一张 PNG、JPEG 或 WebP,以 image_b64 传 data URL 或纯 base64,解码后单图最大 50 MiB,编码字符串不超过 70,000,000 字符。成功返回 HTTP 201。多图逐张上传,再把所有 id 传给视频接口。
BASE="${VIDEO_API_BASE:-https://__ORIGIN__}"
KEY="${VIDEO_API_KEY:?Set VIDEO_API_KEY to the original sk- key}"
set -euo pipefail
UPLOAD=$(python3 -c 'import base64,json; from pathlib import Path; print(json.dumps({"image_b64":base64.b64encode(Path("reference.png").read_bytes()).decode()}))' |
curl --fail-with-body -sS --max-time 90 -X POST "$BASE/v1/files" \
-A "vinted-video-client/1.0" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
--data-binary @-)
IMAGE_ID=$(printf '%s' "$UPLOAD" | jq -er '.image_id | select(type == "string" and length > 0)')
响应字段为 image_id、url、format、size 和 expires_at,id 有效期一小时。同一账号的有效 Key 在过期前可重复使用该 id;已受理的视频任务不受上传过期影响。将 image_id 放进 image_ids,不要把上传响应的 url 当公网图床链接。
创建视频
POST /v1/videos 的请求字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
prompt | string | 是 | 1–6000 字符 |
model | string | 否 | 默认 seedance2.0fast,建议明确传入 |
duration | integer | 否 | 省略时 2.5 默认 30 秒,其他模型默认 5 秒;必须是该模型的合法且有价格的时长 |
ratio | string | 否 | 默认空字符串(模型默认),需要竖屏明确传 9:16 |
resolution | string | 否 | 720p |
camera_movement | string | 否 | auto 或 fixed |
image_ids | string[] | 否 | /v1/files 返回的 id 列表 |
image_url | string | 否 | 单张公网图床直链(png/jpg/webp) |
image_urls | string[] | 否 | 多张公网图床直链;与 image_ids 合并计数 |
不传图片就是文生视频。参考图可传 image_ids(先上传)、image_url/image_urls(公网直链),混用时合并计数。下面的 Bash 示例将参数和操作编号保存到文件;同一订单重试保留这两个文件,新订单在新目录运行。
set -euo pipefail
BASE="${VIDEO_API_BASE:-https://__ORIGIN__}"
KEY="${VIDEO_API_KEY:?Set VIDEO_API_KEY to the original sk- key}"
if [ ! -f order.id ]; then python3 -c 'import uuid; print(uuid.uuid4().hex)' > order.id; fi
if [ ! -f request.json ]; then
jq -n --arg image "${IMAGE_ID:-}" \
'{prompt:"A clean product showcase",model:"seedance2.0fast",duration:5,ratio:"9:16"}
+ (if $image == "" then {} else {image_ids:[$image]} end)' > request.json
fi
OPERATION_ID=$(tr -d '\r\n' < order.id)
test -n "$OPERATION_ID"
JOB=$(curl --fail-with-body -sS --max-time 90 -X POST "$BASE/v1/videos" \
-H "Authorization: Bearer $KEY" \
-H "Idempotency-Key: $OPERATION_ID" \
-A "vinted-video-client/1.0" \
-H "Content-Type: application/json" \
--data-binary @request.json)
JOB_ID=$(printf '%s' "$JOB" | jq -er '.id | select(type == "string" and length > 0)')
printf '%s\n' "$JOB_ID" > job.id
外链必须直接返回图片,不是相册网页或需要登录的地址。image_url/image_urls 单图上限为 25 MiB,与上传接口的 50 MiB 不同。遇到图床防盗链或拉取失败,可改用上传接口。
查询与下载
轮询 GET /v1/videos/{job_id}。中间状态为 queued、running,终态为 succeeded 或 failed。
- 出片是异步的:
POST /v1/videos完成校验和参考图处理后返回 202 和id,不会等到视频生成完。外链图片拉取可能延长提交等待时间。 - 生成通常几十秒到几分钟,
seedance2.5/ 30 秒更久。请轮询GET /v1/videos/{job_id},建议每 3–5 秒一次,总等待预算给到 10 分钟以上。 - 客户端超时建议:连接超时 10 秒、读取超时 ≥ 60 秒;不要给提交或轮询套 30 秒的短超时。
POST一定带唯一Idempotency-Key:网络超时后可安全重试同一请求,不会重复建任务或重复扣费。- 站点前有网关,超长的单条连接可能被断开;请依赖轮询拿终态,下载大文件用
Range断点续传。
set -euo pipefail
BASE="${VIDEO_API_BASE:-https://__ORIGIN__}"
KEY="${VIDEO_API_KEY:?Set VIDEO_API_KEY to the original sk- key}"
JOB_ID=$(tr -d '\r\n' < job.id)
DEADLINE=$((SECONDS + 1800))
while [ "$SECONDS" -lt "$DEADLINE" ]; do
CURRENT=$(curl --fail-with-body -sS --max-time 90 "$BASE/v1/videos/$JOB_ID" \
-H "Authorization: Bearer $KEY" -A "vinted-video-client/1.0")
STATUS=$(printf '%s' "$CURRENT" | jq -er '.status | select(type == "string")')
case "$STATUS" in
succeeded) break ;;
failed) printf '%s\n' "$CURRENT"; exit 1 ;;
queued|running) sleep 5 ;;
*) printf '%s\n' "$CURRENT"; exit 1 ;;
esac
done
if [ "${STATUS:-}" != succeeded ]; then
echo "Still pending. Keep job.id and rerun this polling block."; exit 1
fi
curl --fail-with-body -sS --max-time 600 "$BASE/v1/videos/$JOB_ID/file" \
-H "Authorization: Bearer $KEY" -A "vinted-video-client/1.0" -o result.mp4.part
mv result.mp4.part result.mp4
GET|HEAD /v1/videos/{job_id}/file 也可使用 Key 访问,并支持字节范围。
全能参考 API
沿用账户的 sk- API Key 和积分余额,对所有状态为 active 的正常账户开放,无须逐用户授权。GET /v1/niulai/catalog 返回 access_allowed(账户状态)、enabled(模块总开关)、available(服务可用性)和当前可选模型、时长价格。
| 模型 | API 标识 | 整数时长 | 参考素材与输出 |
|---|---|---|---|
| 牛来pro | niulaipro | 4–15 秒 | 文生视频、首尾帧、多素材参考;自动画幅;768p 默认 10 积分/秒,2K 默认 15 积分/秒 |
牛来pro 的 768p 默认 10 积分/秒,2K 默认 15 积分/秒:4 秒分别为 40、60 积分,15 秒分别为 150、225 积分;管理员可随时修改两档价格,已受理订单保留原扣费。牛来pro 提交前以目录的 resolution_prices[resolution][duration] 为准,其他模型使用 prices;空价格表示该规格未开放。新任务扣积分,失败自动退款。模块总开关关闭后停止新上传和生成,已付费任务继续处理,历史查询、恢复和下载仍可使用。
上传、提交与下载
| 方法与路径 | 用途 |
|---|---|
GET /v1/niulai/catalog | 权限、模型能力和积分价格 |
POST /v1/niulai/assets?kind=image&role=reference | 上传图片、视频或音频原始字节,返回素材 id |
POST /v1/niulai/submit | 创建任务,必传 Idempotency-Key,成功为 HTTP 202 |
GET /v1/niulai/jobs?page=1&size=30 | 本账户任务列表,每页最多 100 条 |
GET /v1/niulai/jobs/{job_id} | 查询状态、扣费、退款和恢复进度 |
GET /v1/niulai/jobs/{job_id}/signed_url | 生成本站下载地址,返回文件 size、sha256、expires_at |
GET /v1/niulai/jobs/{job_id}/file | 下载原始视频,支持 Range,使用 Key 或返回的完整签名地址 |
POST /v1/niulai/jobs/{job_id}/delivered | 完整保存并校验文件后,提交 {"size":字节数,"sha256":"文件摘要"} 确认交付 |
上传使用文件原始字节和对应 Content-Type,不使用 JSON 或 multipart;可带 X-Filename。图片最多 20 MiB、视频 100 MiB、音频 50 MiB,网关硬上限为 128 MiB。支持 JPEG/PNG/WebP 图片和服务能探测的 MP4/MOV/WebM、MP3/M4A/WAV/FLAC/OGG 等音视频格式;下载返回 MP4 二进制流。
牛来pro 的 mode=reference 最多 9 张图片、3 段视频、3 段音频,视频和音频各自合计不超过 15 秒;图片和视频宽高须为 256–5760 像素,音频必须配至少一张图片。mode=frames 只接受最多一张 first 和一张 last 图片,不能与参考素材混用;可只传一帧、两帧或不传素材。
正文必填 model、prompt 和 4–15 秒整数 duration。牛来pro 的 aspect 支持 16:9、9:16、4:3、3:4、1:1、21:9、adaptive;parameters 仅含 resolution,可选 768p 或 2k。牛来1/2(niulai1、niulai2)已停售,新提交返回 422 model_retired,历史牛来1/牛来2订单仍可查询和下载。
门户文本框支持 @图片N、@视频N、@音频N 选择已上传素材;提交前会编译为 Picture N、Video N、Audio N 并绑定素材 ID。直接调用 API 时请传素材 ID 和已编译的提示词。
curl --fail-with-body -sS --max-time 90 "https://__ORIGIN__/v1/niulai/submit" \
-H "Authorization: Bearer sk-your-key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: niulaipro-order-001" \
--data '{"model":"niulaipro","prompt":"主体沿海边步道前行,镜头缓慢跟随","duration":8,"aspect":"16:9","mode":"reference","assets":[],"parameters":{"resolution":"768p"}}'
每个新视频使用独立 Idempotency-Key(1–200 字符),服务端按 API Key 和该头生成操作编号;保存返回的任务 ID。相同 Key 和正文重试返回原订单;更改正文返回 409 idempotency_conflict。超时或 425 operation_pending 后保留原 Key 和正文继续恢复,不能换 Key 重复下单。
queued、running、recovering 都应继续查询;约每 5 秒查询一次。succeeded 后获取签名地址并下载,文件大小与 SHA256 均通过后再确认交付;链接过期可以重新获取。failed 查看 error 和 refunded,不要因下载失败再次创建视频。签名链接无需附带 Key。
Key 的每分钟请求、每日任务和并发限制仍有效。无效或禁用 Key 返回 401 invalid_key;未绑定客户或提交竞态中的账户失效可能返回 403 customer_not_allowed;余额不足返回 402 insufficient_balance;旧模型或未配置价格返回 422 model_retired/unpriced;限流或队列额度不足返回 429,根据 error.type 区分 rpm、daily_cap、concurrency、queue_full。错误响应可带 request_id。总开关关闭返回 503 channel_disabled,已付费任务仍可恢复、查询和下载。
人物照片转彩绘 API
把人物照片转成彩色铅笔手绘风格。沿用账户的 sk- API Key 与积分余额:提交图片、查询任务、下载结果,全部通过下面的接口完成。省略 prompt 即使用本站默认彩铅风格,也可以传入自己的绘图要求。
先查价再提交:GET /v1/image-ai/settings 返回当前 price_per_image,以实际返回值为准。每张受理成功的图片独立扣积分,生成失败自动退款;未受理的图片不扣费,可在 GET /v1/balance 核对流水。该功能不占视频日配额,当前月卡账户暂不支持。
| 接口 | 说明 |
|---|---|
GET /v1/image-ai/settings | 查询当前图片单价与保留时间 |
POST /v1/image-ai/submit | 批量提交,返回 HTTP 202 和逐张受理结果 |
GET /v1/image-ai/batch/{batch_id} | 查询批次中的图片任务 |
GET /v1/image-ai/jobs/{job_id} | 查询单张状态,成功时返回新的短期签名下载地址 |
GET /v1/image-ai/jobs/{job_id}/file | 下载图片;使用 Key 或有效的 exp、sig 签名 |
请求与结果
images_b64:必填数组,1–30 张 PNG、JPEG 或 WebP,元素为纯 Base64 或 data URL。每张解码后最多 20 MiB,整次 JSON 默认最多 96 MiB(含 Base64 体积,服务可调整);大图请拆成小批次。prompt:可选,最多 4,000 字符。留空使用默认彩铅风格;填写则整批使用这段绘图要求。接口不返回本站默认提示词。Idempotency-Key:可选但强烈建议,1–200 字符。每个新批次用新编号;提交超时、响应丢失或重启时,保留原 Key、编号和请求体重试。相同请求重放不会重复扣费;相同编号对应不同正文时返回409 idempotency_conflict。操作编号按 API Key 隔离。202只表示收到批次。逐一检查items[].ok:true时保存id,false时读取error。余额不足或非法图片可能使部分乃至全部图片未受理,不能只看 HTTP 状态。- 图片任务状态为
queued(排队)、processing(生成中)、succeeded(成功)、failed(失败)。每 3–5 秒轮询一次;只有成功且完整下载图片,才算取得结果。 - 成功任务返回
download_url,可能为相对地址;用 URL 解析函数与站点基址拼接,保留全部签名参数。链接过期后重新查询任务获取新地址;也可带 Key 直接访问文件接口。以实际Content-Type保存 PNG、JPEG 或 WebP。 - 同账户的其他有效 Key 可以查询和下载该账户的图片;其他账户的任务一律返回 404。任务与文件默认保留 24 小时,具体以设置返回的
retention_hours为准,请及时保存。
curl:本地图片提交与查询
下面示例使用 Bash、Python 3 和 curl 7.76+。把人物图放在当前目录,命名为 portrait.jpg。新订单使用新目录;超时重试保留已保存的 JSON 与操作编号。
export IMAGE_API_BASE="https://__ORIGIN__"
export IMAGE_API_KEY="sk-your-key"
# 每个订单使用单独目录;重试保留 request.json 和 operation.id,不重新生成。
python3 - <<'PY'
import base64, json, uuid
from pathlib import Path
request, operation = Path("image-request.json"), Path("image-operation.id")
if not request.exists():
body = {"images_b64": [base64.b64encode(Path("portrait.jpg").read_bytes()).decode("ascii")]}
request.write_text(json.dumps(body), encoding="utf-8")
if not operation.exists():
operation.write_text(uuid.uuid4().hex, encoding="ascii")
PY
curl --fail-with-body -sS --max-time 30 "$IMAGE_API_BASE/v1/image-ai/settings" \
-H "Authorization: Bearer $IMAGE_API_KEY"
curl --fail-with-body -sS --max-time 90 "$IMAGE_API_BASE/v1/image-ai/submit" \
-H "Authorization: Bearer $IMAGE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(cat image-operation.id)" \
--data-binary @image-request.json
# 从返回 items 中取 ok=true 的 id;每 5 秒查询一次,直到 succeeded 或 failed。
JOB_ID="替换为已受理的图片任务id"
curl --fail-with-body -sS --max-time 30 \
"$IMAGE_API_BASE/v1/image-ai/jobs/$JOB_ID" \
-H "Authorization: Bearer $IMAGE_API_KEY"
# succeeded 后可直接用 Key 下载;响应 Content-Type 决定真实图片格式。
curl --fail-with-body -sS --max-time 90 \
"$IMAGE_API_BASE/v1/image-ai/jobs/$JOB_ID/file" \
-H "Authorization: Bearer $IMAGE_API_KEY" -o portrait-result.image
Python:批量生成、轮询与下载
Python 3.10+,先执行 python -m pip install requests,把下方代码保存为 image_client.py。设置 IMAGE_API_BASE、IMAGE_API_KEY 后运行 python image_client.py portrait.jpg second.png。PowerShell 设置环境变量时使用 $env:IMAGE_API_KEY = "sk-your-key"。
可选 IMAGE_PROMPT 设置整批绘图要求,IMAGE_WAIT_SECONDS 设置本次最多等待秒数(默认 1800),IMAGE_OUTPUT 设置下载目录。状态文件默认 image-order-001.json:重跑会恢复已有任务;新订单须改用新的 IMAGE_STATE 文件名。状态文件含输入图片与请求体,应保存在自己的受控目录;每个订单同一时间只运行一个进程。
总等待到期不会取消生成,也不代表失败。保留状态文件和原 API Key 再次运行即可继续查询或下载;未受理的图片与已确认失败的图片不会在重跑时自动创建新任务。
"""Python 3.10+; install: python -m pip install requests"""
import base64
import json
import os
import sys
import time
import uuid
from pathlib import Path
from urllib.parse import quote, urljoin, urlsplit
import requests
BASE = os.environ.get("IMAGE_API_BASE", "https://__ORIGIN__").rstrip("/") + "/"
KEY = os.environ.get("IMAGE_API_KEY", "").strip()
STATE = Path(os.environ.get("IMAGE_STATE", "image-order-001.json"))
OUT = Path(os.environ.get("IMAGE_OUTPUT", "image-results"))
WAIT = float(os.environ.get("IMAGE_WAIT_SECONDS", "1800"))
if not KEY.startswith("sk-") or WAIT <= 0:
raise SystemExit("Set IMAGE_API_KEY to your own sk- key and use a positive wait limit.")
origin = urlsplit(BASE)
if origin.scheme not in ("https", "http") or not origin.netloc:
raise SystemExit("IMAGE_API_BASE must be an HTTP(S) origin, without /v1.")
if origin.path != "/" or origin.query or origin.fragment:
raise SystemExit("IMAGE_API_BASE must contain only the site origin.")
def save():
STATE.parent.mkdir(parents=True, exist_ok=True)
temp = STATE.with_name(STATE.name + ".tmp")
temp.write_text(json.dumps(state, ensure_ascii=False), encoding="utf-8")
temp.replace(STATE)
if STATE.exists():
state = json.loads(STATE.read_text(encoding="utf-8"))
if state["base"] != BASE:
raise SystemExit("Use the original base URL, API Key, and state file to resume.")
else:
paths = [Path(name) for name in sys.argv[1:]]
if not 1 <= len(paths) <= 30:
raise SystemExit("Usage: python image_client.py photo.png [photo2.jpg ...]; 1-30 images.")
images = []
for path in paths:
data = path.read_bytes()
if not 0 < len(data) <= 20 * 1024 * 1024:
raise SystemExit(str(path) + ": decoded image must be between 1 byte and 20 MiB.")
images.append(base64.b64encode(data).decode("ascii"))
body = {"images_b64": images}
if os.environ.get("IMAGE_PROMPT", "").strip():
body["prompt"] = os.environ["IMAGE_PROMPT"]
if len(body.get("prompt", "")) > 4000:
raise SystemExit("IMAGE_PROMPT must not exceed 4000 characters.")
if len(json.dumps(body).encode("utf-8")) > 96 * 1024 * 1024:
raise SystemExit("Split these images into smaller batches; JSON exceeds the default 96 MiB limit.")
state = {"base": BASE, "operation": uuid.uuid4().hex,
"body": body, "accepted": None, "done": {}}
save()
deadline = time.monotonic() + WAIT
session = requests.Session()
session.headers.update({"Authorization": "Bearer " + KEY,
"User-Agent": "image-client/1.0"})
def remaining():
seconds = deadline - time.monotonic()
if seconds <= 0:
raise TimeoutError("Total wait ended. Rerun with the SAME IMAGE_STATE and API Key.")
return seconds
def safe_url(path):
url = urljoin(BASE, path)
target = urlsplit(url)
if (target.scheme, target.netloc) != (origin.scheme, origin.netloc):
raise RuntimeError("Refusing a result URL outside the configured site.")
return url
def error_message(response):
try:
value = response.json()
error = value.get("error", value.get("detail", {}))
if isinstance(error, dict):
return str(error.get("message") or error.get("type") or "Request failed")
return str(error)
except (ValueError, AttributeError):
return "Non-JSON response; retain the HTTP status for support."
def call(method, path, **kwargs):
budget = remaining()
response = session.request(method, safe_url(path), timeout=(min(10, budget), min(90, budget)),
allow_redirects=False, **kwargs)
if not 200 <= response.status_code < 300:
raise RuntimeError("HTTP " + str(response.status_code) + ": " + error_message(response))
return response
def get_json(method, path, **kwargs):
with call(method, path, **kwargs) as response:
value = response.json()
if not isinstance(value, dict):
raise RuntimeError("Unexpected JSON response. Keep the same state file to resume.")
return value
def download(job):
link = job.get("download_url")
if not link:
raise RuntimeError("Succeeded job has no download_url; query this job again.")
with call("GET", link, stream=True) as response:
kind = response.headers.get("Content-Type", "").split(";", 1)[0].lower()
extension = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}.get(kind)
if not extension:
raise RuntimeError("Unexpected image Content-Type: " + kind)
OUT.mkdir(parents=True, exist_ok=True)
dest = OUT / (job["id"] + extension)
partial = dest.with_suffix(dest.suffix + ".part")
size = 0
prefix = b""
with partial.open("wb") as file:
for chunk in response.iter_content(64 * 1024):
remaining()
if chunk:
prefix = (prefix + chunk)[:12]
file.write(chunk)
size += len(chunk)
valid = ((kind == "image/png" and prefix.startswith(b"\x89PNG\r\n\x1a\n"))
or (kind == "image/jpeg" and prefix.startswith(b"\xff\xd8\xff"))
or (kind == "image/webp" and prefix[:4] == b"RIFF" and prefix[8:12] == b"WEBP"))
if not size or not valid:
raise RuntimeError("Downloaded bytes do not match the announced image format.")
expected = response.headers.get("Content-Length")
if expected and response.headers.get("Content-Encoding", "identity") == "identity":
if size != int(expected):
raise RuntimeError("Incomplete download. Rerun with the same state file.")
partial.replace(dest)
return str(dest.resolve())
try:
if state["accepted"] is None:
settings = get_json("GET", "/v1/image-ai/settings")
print("Current credits per image:", settings["price_per_image"])
accepted = get_json("POST", "/v1/image-ai/submit", json=state["body"],
headers={"Idempotency-Key": state["operation"]})
if not isinstance(accepted.get("items"), list) or not accepted.get("batch_id"):
raise RuntimeError("Missing batch receipt. Keep the same state file to resume.")
state["accepted"] = accepted
save()
items = state["accepted"]["items"]
pending = set()
failed = any(not item.get("ok") for item in items)
for index, item in enumerate(items, 1):
if not item.get("ok"):
print("NOT ACCEPTED", index, item.get("error", "Unknown error"))
elif not item.get("id"):
raise RuntimeError("Accepted item has no id.")
else:
job_id = str(item["id"])
result = state["done"].get(job_id)
if result and result["status"] == "failed":
failed = True
print("FAILED", job_id, result.get("error", "Generation failed"))
elif result and Path(result.get("file", "")).is_file():
print("SAVED", result["file"])
else:
pending.add(job_id)
while pending:
for job_id in list(pending):
job = get_json("GET", "/v1/image-ai/jobs/" + quote(job_id, safe=""))
status = job.get("status")
if status in ("queued", "processing"):
continue
if status == "failed":
failed = True
state["done"][job_id] = {"status": status, "error": job.get("error", "")}
print("FAILED", job_id, job.get("error", "Generation failed"))
elif status == "succeeded":
path = download(job)
state["done"][job_id] = {"status": status, "file": path}
print("SAVED", path)
else:
raise RuntimeError("Unknown job status: " + str(status))
pending.remove(job_id)
save()
if pending:
time.sleep(min(5, remaining()))
raise SystemExit(1 if failed else 0)
except (requests.RequestException, RuntimeError, TimeoutError, ValueError, OSError) as exc:
raise SystemExit(str(exc) + "\nKeep IMAGE_STATE and the original API Key to resume; do not create a new order.")
finally:
session.close()
号池状态
需单独申请只读 Key。
GET /v1/pool/status 返回实时号池规模与可用量,用于向你的客户证明号池真实。
sk- Key,而是单独申请的号池只读 Key(pk- 开头)。两套凭据物理隔离:pk- 调不动 /v1/videos,不会消耗任何额度;sk- 也打不开本接口。需要接入请联系我们签发。curl --fail-with-body -sS "$BASE/v1/pool/status" \
-H "Authorization: Bearer $POOL_KEY"
{
"generated_at": 1756531200,
"total": 48,
"available": 31,
"status_counts": {"active": 31, "busy": 2, "cooling": 5, "inactive": 10},
"availability_counts": {"active": 31, "busy": 2, "cooling": 5, "inactive": 3,
"recovery_pending": 4, "quota_exhausted": 1, "quota_frozen": 1, "unavailable": 1},
"quota": {"total": 192, "remaining": 118, "remaining_available": 84}
}
| 字段 | 含义 |
|---|---|
generated_at | 快照时间(Unix 秒) |
total | 号池账号总数 |
available | 当前可调度账号数 |
status_counts | 兼容原有四类状态:active、busy、cooling、inactive;inactive 包含所有当前不可调度账号,不代表永久失效 |
availability_counts | 当前可用性原因的详细数量(见下表) |
quota.total | 账号配置额度之和,不代表上游已确认的可用额度 |
quota.remaining | 本地估算剩余额度;等待恢复的账号不会仅因重置时间到期而加满 |
quota.remaining_available | active 账号的本地估算剩余额度,可能包含获准尝试恢复的账号 |
available 与调度器当前可派单账号数一致,可能包含已分配恢复尝试名额的账号,不保证上游受理成功。两组统计各自互斥,分别合计为 total。新增的四类不可用原因在原有 status 中统一归入 inactive。
| 可用性原因 | 含义 |
|---|---|
| active | 当前可派单,包括获准尝试恢复的账号 |
| busy | 任务、账号租约或内部占用状态尚未释放 |
| cooling | 临时限流或退避中 |
| inactive | 账号被停用、暂停或登录状态不可用 |
recovery_pending | 额度恢复尚待验证,当前未获派单资格;不代表账号丢失或永久失效 |
quota_exhausted | 本地记录额度已耗尽,尚未进入恢复窗口 |
quota_frozen | 额度暂时冻结,等待恢复窗口 |
unavailable | 暂不满足其他派单条件 |
逐账号明细
带 accounts=true 可拿到每个账号一行的脱敏明细,用 limit(1–500,默认 100)和 offset(默认 0)分页。
curl --fail-with-body -sS "$BASE/v1/pool/status?accounts=true&limit=50" \
-H "Authorization: Bearer $POOL_KEY"
{
"generated_at": 1756531200,
"total": 48, "available": 31,
"status_counts": {"active": 31, "busy": 2, "cooling": 5, "inactive": 10},
"availability_counts": {"active": 31, "busy": 2, "cooling": 5, "inactive": 3,
"recovery_pending": 4, "quota_exhausted": 1, "quota_frozen": 1, "unavailable": 1},
"quota": {"total": 192, "remaining": 118, "remaining_available": 84},
"accounts": [
{"id": "acc_7f3a91c4", "status": "active", "availability_reason": "active", "quota_total": 4, "quota_remaining": 3, "completed": 214},
{"id": "acc_1b8d05e6", "status": "cooling", "availability_reason": "cooling", "quota_total": 4, "quota_remaining": 0, "completed": 189}
],
"page": {"limit": 50, "offset": 0, "returned": 48, "total": 48}
}
| 字段 | 含义 |
|---|---|
accounts[].id | 稳定且不可反推的账号标识 |
accounts[].status | 原有四类状态之一:active、busy、cooling、inactive |
accounts[].availability_reason | 上表可用性原因之一,区分实际占用、等待恢复及其他不可用原因 |
accounts[].quota_total | 该账号配置额度 |
accounts[].quota_remaining | 该账号本地估算剩余额度,不是上游余额承诺 |
accounts[].completed | 该账号累计成功出片数 |
page.returned / page.total | 本次返回行数 / 号池总数 |
id 是截断的 HMAC:跨多次轮询保持稳定,可以长期跟踪同一个账号,但无法反推出我们这边的任何信息。账号凭据、地区、线路一律不会出现在任何响应里。号池 Key 可以签发成“仅汇总”权限。这类 Key 请求 accounts=true 会返回 403 accounts_not_allowed,但聚合查询照常可用。
| 状态 | type | 含义 |
|---|---|---|
| 401 | invalid_pool_key | 号池 Key 缺失、无效、已禁用或已撤销 |
| 403 | accounts_not_allowed | 该 Key 只能读聚合数字 |
| 429 | rate_limited | 号池 Key 超过每分钟调用上限 |
号池 Key 有独立的 RPM 限制,与出片 Key 互不影响。Key 可以禁用后再启用;已撤销的 Key 永久失效。
错误与限制
| 状态 | 含义 | 怎么办 |
|---|---|---|
| 202 | 任务已入队(创建成功) | 开始轮询,别当失败 |
| 400 | 图片编码/格式无效,或图床直链拉取失败 | 检查 base64 或 URL(仅 png/jpg/webp) |
| 401 | Key 缺失、无效或已删除 | 检查 Authorization 头 |
| 402 | 余额不足 | 充值后重试 |
| 403 | 账号访问受限,或下载签名无效/过期 | 签名问题重新获取 /signed_url;账号问题联系支持 |
| 404 | 任务/图片不存在、已过期或不属于当前账号;/file 也可能尚未就绪 | 先查询任务状态,再确认 id 与归属 |
| 409 | 相同幂等请求仍在处理,或视频尚未就绪 | 稍后重试 / 继续轮询 |
| 410 | 原提交已处理,但任务记录已删除 | 不再自动重建;确需生成新视频时创建新订单 |
| 413 / 422 | 请求过大或字段不合法 | 按错误 message 修正字段 |
| 429 | 触发 concurrency、daily_cap 或 rpm 限制 | 按 Retry-After 等待后重试 |
| 502 / 504 | 视频下载临时不可用 | 短暂等待后重试下载 |
{"error":{"code":"401","type":"invalid_key","message":"invalid api key","request_id":"req-example"}}
status: failed,此时 error 是字符串。收到 429 按 Retry-After 头或 JSON 顶层 retry_after 等待。反馈时提供请求方法、路径、HTTP 状态码、error.type、request_id 和任务 id,隐藏 Key。返回 HTML、403 页面或空响应
先检查完整请求 URL,设置 User-Agent: vinted-video-client/1.0,用余额查询确认网络与鉴权。网关响应可能不是 JSON,保留 HTTP 状态、响应头与 X-Request-ID 后反馈;创建响应不明确时不要更换操作编号。
接口参考
接口字段与响应结构见下方参考。导入 HTTP 客户端使用 https://__ORIGIN__/openapi.json;模型可用组合与价格以 /v1/catalog 和带 Key 的 /v1/pricing 实时返回为准。