1. 获取密钥
向服务方申请调用密钥(一个调用方一个)。每次请求在请求头里带上:
Authorization: Bearer <你的密钥>
按密钥计费
每个任务的预估与实际费用都记在对应密钥下,可随时查询当天用量。
数据隔离
只能看到自己上传的素材和自己创建的任务,访问他人资源一律返回 404。
只放服务端
密钥等同账户,不要写进网页、App 或前端代码;疑似泄露请立即联系服务方更换。
2. 快速开始(curl)
以“换人物、保留原声”为例。先设置两个环境变量:
export BASE=https://remake-api.huashiruida.com
export TOKEN=你的密钥
- 上传原视频
curl -s $BASE/v1/assets -H "Authorization: Bearer $TOKEN" \ -F kind=video -F rights_confirmed=true -F contains_person=true \ -F file=@source.mp4 # → {"id": "a_3f9c…", "kind": "video", "duration_seconds": 12.4, …} - 上传新人物照片
curl -s $BASE/v1/assets -H "Authorization: Bearer $TOKEN" \ -F kind=image -F rights_confirmed=true -F file=@person.jpg
- (建议)预检:看参数、提示、预估费用,不建任务、不计费
curl -s $BASE/v1/preflight -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" -d '{ "model": "seedance-2.5", "source_video": "a_原视频ID", "person_image": "a_人物照片ID", "replace": ["person"], "audio": {"mode": "preserve_original"}, "resolution": "720p", "rights_confirmed": true }' # → {"plan": {…, "warnings": [...]}, "cost": {"estimated_cny": …}, …} - 创建任务(必须带幂等键)
curl -s $BASE/v1/video-remakes -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-20261014-0001" \ -d '{ …同上… }' # → 202 {"id": "r_8d1e…", "state": "queued", "cost": {"estimated_cny": …}, …}网络超时等不确定情况,用同一个幂等键重试,不会重复建任务、不会重复扣费。
- 轮询状态(间隔 ≥ 10 秒)
curl -s $BASE/v1/video-remakes/任务ID -H "Authorization: Bearer $TOKEN"
- 下载结果:状态为
succeeded后,用返回的result_url(同样带密钥)下载。curl -s -o result.mp4 "$BASE/v1/assets/a_结果ID/content" -H "Authorization: Bearer $TOKEN"
3. 完整示例
import os, time, uuid, requests
BASE = "https://remake-api.huashiruida.com"
H = {"Authorization": f"Bearer {os.environ['REMAKE_TOKEN']}"}
def upload(path, kind):
with open(path, "rb") as f:
r = requests.post(f"{BASE}/v1/assets", headers=H, files={"file": f},
data={"kind": kind, "rights_confirmed": "true"}, timeout=300)
r.raise_for_status()
return r.json()["id"]
body = {
"model": "seedance-2.5",
"source_video": upload("source.mp4", "video"),
"person_image": upload("person.jpg", "image"),
"replace": ["person"],
"audio": {"mode": "preserve_original"},
"resolution": "720p",
"rights_confirmed": True,
}
key = f"job-{uuid.uuid4().hex}" # 保存下来:超时重试时复用同一个键
r = requests.post(f"{BASE}/v1/video-remakes", json=body,
headers={**H, "Idempotency-Key": key}, timeout=60)
r.raise_for_status()
task = r.json()
while task["state"] in ("queued", "submitting", "running", "postprocessing"):
time.sleep(15)
task = requests.get(f"{BASE}/v1/video-remakes/{task['id']}", headers=H, timeout=30).json()
if task["state"] == "succeeded":
video = requests.get(BASE + task["result_url"], headers=H, timeout=300)
open("result.mp4", "wb").write(video.content)
print("完成,费用:", task["cost"], "提示:", task["warnings"])
else:
print("未成功:", task["state"], task.get("error"))// Node 18+
import { readFile, writeFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const BASE = "https://remake-api.huashiruida.com";
const H = { Authorization: `Bearer ${process.env.REMAKE_TOKEN}` };
async function upload(path, kind, type) {
const form = new FormData();
form.append("kind", kind);
form.append("rights_confirmed", "true");
form.append("file", new Blob([await readFile(path)], { type }), path);
const r = await fetch(`${BASE}/v1/assets`, { method: "POST", headers: H, body: form });
if (!r.ok) throw new Error(JSON.stringify(await r.json()));
return (await r.json()).id;
}
const body = {
model: "seedance-2.5",
source_video: await upload("source.mp4", "video", "video/mp4"),
person_image: await upload("person.jpg", "image", "image/jpeg"),
replace: ["person"],
audio: { mode: "preserve_original" },
resolution: "720p",
rights_confirmed: true,
};
const key = `job-${randomUUID()}`; // 超时重试时复用同一个键
let task = await (await fetch(`${BASE}/v1/video-remakes`, {
method: "POST",
headers: { ...H, "Content-Type": "application/json", "Idempotency-Key": key },
body: JSON.stringify(body),
})).json();
while (["queued", "submitting", "running", "postprocessing"].includes(task.state)) {
await new Promise((s) => setTimeout(s, 15000));
task = await (await fetch(`${BASE}/v1/video-remakes/${task.id}`, { headers: H })).json();
}
if (task.state === "succeeded") {
const v = await fetch(BASE + task.result_url, { headers: H });
await writeFile("result.mp4", Buffer.from(await v.arrayBuffer()));
console.log("完成", task.cost, task.warnings);
} else {
console.log("未成功", task.state, task.error);
}机器可读的完整接口定义:/openapi.json(可直接导入 Postman、Apifox 或交给智能体);交互式文档:/docs。
4. 请求参数
上传素材 POST /v1/assets(multipart/form-data)
| 字段 | 说明 |
|---|---|
file | 视频 MP4/MOV(H.264/H.265,≤ 30 秒);图片 PNG/JPEG/WebP;音频 WAV/MP3(≤ 30 秒)。单文件 ≤ 200MB,扩展名必须与实际内容一致。 |
kind | video / image / audio |
rights_confirmed | 必须为 true:确认拥有该素材(含肖像、声音)的使用授权。 |
contains_person | 是否含真人,默认 true。 |
创建任务 POST /v1/video-remakes(JSON,与预检相同)
| 字段 | 说明 |
|---|---|
source_video | 必填,原视频素材 ID。原视频不会被覆盖。 |
replace | 要替换的内容,可多选:person、product、background。选了哪项就要提供对应素材。 |
person_image | 新人物照片 ID(配合 person)。 |
product_images | 商品图片 ID 列表,最多 3 张(配合 product)。 |
background_image | 新场景图片 ID(配合 background)。 |
audio | 声音策略,见第 6 节。默认 {"mode":"preserve_original"}。 |
model | seedance-2.5(默认,最长 30 秒)、seedance-2.0、seedance-fast、seedance-mini(最长 15 秒)。实际可用模型以 GET /v1/models 为准。 |
resolution | 默认 720p。seedance-2.5:480p/720p/1080p;seedance-2.0:另支持 4k;fast/mini:480p/720p。 |
ratio | adaptive(默认,跟随原视频)、16:9、9:16、4:3、3:4、1:1、21:9。 |
duration_seconds | 可选,4–30 的整数;默认跟随原视频。 |
instructions | 可选,补充要求,≤ 2000 字。 |
pipeline | generate(默认,模型重新生成画面)或 audio_only(只换音轨,见第 6 节)。 |
rights_confirmed | 必须为 true。 |
请求头 Idempotency-Key 必填:8–128 位,只能包含字母、数字和 _ . : -。同键同参重放返回同一任务(200);同键不同参数返回 409。
5. 任务状态
| 状态 | 含义 | 调用方怎么做 |
|---|---|---|
queued | 已受理,排队中 | 继续轮询 |
submitting / running | 提交模型 / 生成中 | 继续轮询 |
postprocessing | 下载结果、处理音轨 | 继续轮询 |
succeeded | 候选视频可下载 | 下载后人工看片:人物、口型、动作、声音是否完整 |
simulated | 演示模式,只校验了请求 | 没有视频,不算成功 |
failed | 明确失败(参数、审核未通过、排队超时等) | 看 error.code,修改后用新的幂等键重新提交 |
postprocessing_failed | 模型已出片,下载或音轨处理失败 | 调用 POST /v1/video-remakes/{id}/retry-postprocessing,不会重新生成、不会重复计费 |
needs_review | 无法确认模型侧是否已创建或完成 | 不要自动重提,把任务 ID 发给服务方核对 |
其他接口:GET /v1/video-remakes 列出自己的任务;DELETE /v1/assets/{id} 删除自己的素材或结果(成片确认后建议删除人像和声音素材;仍被未完成任务使用时返回 409)。
6. 声音策略 audio.mode
| 值 | 效果 | 注意 |
|---|---|---|
preserve_original | 成片保留原视频的声音 | — |
replace_uploaded | 上传一段完整口播录音(audio.asset_id),成片整条音轨换成这段录音 | 不保留原背景音乐;不是音色克隆;录音不能长于视频,不会截断 |
model_generated | 由模型生成声音,可附音色参考(audio.asset_id)和台词 audio.transcript | 不保证音色一致 |
mute | 去掉声音 | 需同时选择至少一项画面替换 |
只换声音、不重新生成画面:请求体写
"pipeline": "audio_only" 并配 replace_uploaded,不要带 model、resolution 等参数。不调用模型、没有模型费用,通常几秒完成;但嘴型仍是原视频的,只适合画外音,或按原稿原节奏重录的口播。所有结果都不承诺口型精确对齐(返回 lip_sync_verified: false),返回的 warnings 请展示给最终使用者。
7. 费用与额度
- 创建任务时按预估金额预占当天额度;模型返回实际用量后按实际结算。预估值可在预检的
cost.estimated_cny看到。 - 任务详情中的
cost:estimated_cny预估、actual_cny实际、basis计费依据(provider_usage已按实际用量结算;estimate尚未结算;not_submitted未提交到模型、已释放;not_billed演示或只换音轨,不计费)。 GET /v1/budget查看本密钥今天(北京时间)已占用的金额和每日上限。- 额度用完时创建任务返回 429
BUDGET_EXCEEDED/TENANT_BUDGET_EXCEEDED,第二天自动恢复。单价与额度以与服务方的约定为准。
8. 错误处理
所有错误统一格式,按 error.code 判断,不要解析 message 文本:
{"error": {"code": "VALIDATION_FAILED", "message": "Invalid request", "details": [...]}}
| HTTP | 常见 code | 处理 |
|---|---|---|
| 401 | UNAUTHORIZED | 密钥缺失或无效 |
| 404 | ASSET_NOT_FOUND、TASK_NOT_FOUND | 资源不存在,或不属于你的密钥 |
| 409 | IDEMPOTENCY_CONFLICT、ASSET_IN_USE、INVALID_TASK_STATE | 换幂等键 / 等任务结束 / 检查状态 |
| 413 | UPLOAD_TOO_LARGE、REQUEST_TOO_LARGE | 压缩或裁短素材 |
| 422 | VALIDATION_FAILED、INVALID_MEDIA、UNSUPPORTED_FILE_TYPE、LONG_MEDIA_NOT_SUPPORTED、UNSUPPORTED_RESOLUTION、NARRATION_TOO_LONG、RIGHTS_REQUIRED | 按提示修改参数或素材 |
| 429 | RATE_LIMIT、QUEUE_FULL、DAILY_LIMIT、BUDGET_EXCEEDED、TENANT_BUDGET_EXCEEDED | 稍后重试;额度类次日恢复 |
| 503 | PAID_CALLS_DISABLED、PROVIDER_NOT_CONFIGURED、PRICE_UNKNOWN | 该模型暂未开放,联系服务方 |
| 507 | STORAGE_LIMIT | 删除不再需要的素材,或联系服务方 |
写接口(上传、创建任务、删除、重试)每个密钥每分钟最多 30 次。
9. 在线试用
密钥只保存在本页内存中,刷新或关闭页面即清除;只发往本服务,不会发给第三方。以下操作都是只读查询,不产生费用。
(结果显示在这里)
10. 限制与说明
- 单段素材最长 30 秒;长视频需先自行切分。
- 生成结果是“候选视频”,需要人工审核后再使用;口型、音色不保证与原片一致。
- 暂不提供:回调通知、批量组合、长视频自动拆镜、只换人声保留背景音乐、自动口型校准。
- 上传素材即确认已获得其中人物肖像与声音的使用授权。