快速开始
一把 API 密钥、三个请求就能生成一段视频:先报价,再创建生成任务,然后轮询直到完成。
1. 创建 API 密钥
在控制台打开 开发者 → API 密钥,新建一把密钥。每个请求都以 Bearer token 的方式带上它。生成费用从你的钱包扣除,每把密钥都可以设消费上限。
2. 获取报价
curl https://api.makernice.com/v1/generations/quote \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"seedance-2.5","mode":"t2v","prompt":"A slow dolly shot through a greenhouse","duration":5,"resolution":"720p"}'
# → { "id": "qt_…", "amount": "500000", "expires_at": "…", "facts": { "quantity": 5, … } }金额以美元计,单位是百万分之一美元(micro-USD):1,000,000 = $1.00。报价会把价格锁定 12 分钟。
3. 创建生成任务
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Idempotency-Key: 7c1e9a2b-order-1842" \
-H "Content-Type: application/json" \
-d '{"quote_id":"qt_…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"A slow dolly shot through a greenhouse","duration":5,"resolution":"720p"}}'
# → 202 { "id": "gen_…", "status": "queued", "billing": "held", … }报价金额会先从余额里冻结。生成失败,或在排队时取消,冻结的金额全额释放。用同一个 Idempotency-Key 重试,返回的是同一个任务,不会重复扣费。
4. 轮询直到完成
curl https://api.makernice.com/v1/generations/gen_… \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# status:queued → generating → moderating → succeeded | failed | canceled
# 成功后:带上密钥请求 outputs[0].url,会 302 跳转到文件5. 下载文件
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"鉴权
除 GET /v1/models 和 GET /v1/pricing 外,每个请求都要在 Authorization 头里带上 API 密钥。密钥的格式是 mn- 加 48 位字母和数字。
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY"- 完整密钥只在创建时显示一次。我们只保存它的指纹,丢了无法找回:新建一把,再删掉旧的。
- 密钥缺失、格式不对、不存在、已禁用、已过期或已删除,一律返回相同的
401 invalid_api_key。禁用或删除密钥后,下一个请求立即生效。 - 请从你的服务端调用。接口不接受浏览器跨域请求(不设 CORS),不要把密钥放进前端代码。
- 同一账户下的所有密钥看到的是同一批任务:任何一把密钥都能查询、列出、取消账户里的全部任务,包括在控制台创建的。
- 在控制台可以给密钥设过期时间和消费上限。密钥的「已花费」= 它创建的任务中已扣款与冻结中金额之和;创建任务会超出上限时返回
402 key_spend_limit_reached。失败和已取消的任务不计入。
价格与计费
- 按美元计费。所有金额都是以百万分之一美元(micro-USD)为单位的整数字符串:
1,000,000= $1.00。请按整数或十进制数解析,不要用浮点数。 - 视频按秒计价(数量 = 时长),图片按次计价(每个任务一张)。价格 = 单价 × 数量。
- 报价把价格锁定 12 分钟(
expires_at)。任务按报价金额计费,期间调价不影响。 - 创建任务时金额从余额冻结(
billing: held);任务成功后扣款(charged);失败或取消则全额释放(released)。任务处于checking时金额保持冻结。
GET /v1/pricing 返回当前价格版本的各组合单价,不需要 API 密钥,结果可能被缓存最长 60 秒。official_unit_price(可能没有)是同一组合的官方参考价,仅供对比,实际按 unit_price 计费。
{
"price_version": 3,
"data": [
{ "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "unit": "second", "unit_price": "100000", "unit_price_usd": "0.10", "official_unit_price": "120000" }
]
}获取报价
提交生成参数,响应会告诉你这次生成的确切费用。每个报价只能用于一个生成任务。
| 字段 | 类型 | 说明 |
|---|---|---|
model | string | 必填。GET /v1/models 里的模型 id。 |
mode | string | 必填。t2v、i2v、flf、r2v、t2i 或 i2i,各模型支持的模式见模型列表。 |
prompt | string | 必填。默认 1–4,000 个字符,部分模型更低(见 GET /v1/models 的 maxPromptChars)。 |
duration | integer | 仅视频。秒数,须在模型允许的范围内;不填用模型默认值。 |
resolution | string | 模型 resolutions 中的一个;不填用 defaultResolution。模型没有列出分辨率时不能传。 |
aspect_ratio | string | 该模式 aspectRatios 中的一个。 |
image_url | string | 首帧。i2v 和 flf 必填。 |
last_image_url | string | 尾帧。flf 必填。 |
reference_images | string[] | r2v 和 i2i 必填,不超过该模式的 maxReferences。 |
seed | integer | 可选。0–2,147,483,647。同一个 seed 加相同参数,结果更接近。 |
generate_audio | boolean | 可选。视频是否生成音轨。 |
prompt_expansion | string | 可选。off、balanced 或 quality:模型改写提示词的程度。 |
negative_prompt | string | 可选。最多 2,000 个字符,描述不想出现的内容。 |
watermark | boolean | 可选。加上服务商的 AI 生成水印。 |
最后五个是可选的附加参数,每个模型只支持其中一部分:GET /v1/models 在每个模式、每个分辨率的 prices[].options 里列出可用的参数、 可选值和默认值。没有任何线路能执行的参数返回 400 invalid_request;和其它字段一样,改了附加参数需要重新报价。
图片传可公开访问的 https 地址(最长 2,048 个字符)。该模式用不到的字段和未知字段都会被拒绝,返回 400 invalid_request, 输入字段的问题由 error.details 以 { path, message } 逐条列出(其它 invalid_request 的 details 格式可能不同,也可能没有)。没有定价的组合返回 422 price_unavailable。
每张图片须是公网可访问的 https 地址,内容为 JPEG、PNG 或 WebP 静态图片(不支持动画 WebP 和 GIF),不超过 10 MB, 且由该地址直接返回:我们不跟随跳转。我们会抓取每张图片一次并保存副本,内容审核与模型生成都只用这份副本。 图片无法使用时任务失败,error.code 为 invalid_request,error.input 标出是哪张图,冻结金额全额释放。
curl https://api.makernice.com/v1/generations/quote \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"seedance-2.5","mode":"t2v","prompt":"A slow dolly shot through a greenhouse","duration":5,"resolution":"720p"}'{
"id": "qt_6f1c…",
"amount": "500000",
"price_version": 3,
"expires_at": "2026-10-09T09:24:41.000Z",
"facts": { "model": "seedance-2.5", "mode": "t2v", "resolution": "720p", "variant": "default", "unit": "second", "quantity": 5 }
}创建生成任务
用报价和同一份输入创建生成任务。必须带 Idempotency-Key 请求头(见幂等)。
curl https://api.makernice.com/v1/generations \
-H "Authorization: Bearer $MAKERNICE_API_KEY" \
-H "Idempotency-Key: 7c1e9a2b-order-1842" \
-H "Content-Type: application/json" \
-d '{"quote_id":"qt_6f1c…","input":{"model":"seedance-2.5","mode":"t2v","prompt":"A slow dolly shot through a greenhouse","duration":5,"resolution":"720p"}}'- 成功返回
202和任务(status: queued、billing: held)。用同一个 Idempotency-Key 重试返回200和原来的任务。 - 提示词可以改;改动影响价格的参数(模型、模式、分辨率、时长)会返回
422 quote_mismatch。 - 报价已被使用返回
409 quote_already_used,已过期返回410 quote_expired,不属于你的账户返回404 quote_not_found。 - 余额不足返回
402 insufficient_balance;超出密钥消费上限返回402 key_spend_limit_reached。 - 可选的顶层字段
callback_url(https,不超过 2048 个字符,地址里不能带账号密码,不能指向内网或保留地址):任务结束时我们把任务 POST 到这里,优先于账户的 webhook 地址。见Webhook。
查询状态
轮询任务直到进入最终状态。每隔几秒查一次即可,读操作的频率限制见「频率限制」。
| 状态 | 含义 |
|---|---|
queued | 排队等待开始,此时还能取消。 |
generating | 模型正在生成。 |
moderating | 生成已完成,产物正在安全审核,审核完成前 outputs 为空数组,继续轮询。 |
succeeded | 已完成,金额已扣款,outputs 可以下载。 |
failed | 未能完成,冻结金额已释放;error 里有错误码和说明。 |
canceled | 在排队时被取消,冻结金额已释放。 |
checking | 我们正在核实结果,核实完成前金额保持冻结。 |
失败任务的 error.code 是以下之一:invalid_request(模型拒绝了请求,调整提示词或参数后重试)、content_rejected(未通过内容审核或模型方的安全策略,冻结金额已释放)、timeout、upstream_unavailable、generation_failed。content_rejected 另带 error.reason 说明被拦的是什么:prompt(提示词)、input_image(参考图)、model(模型方的安全策略);提交时提示词被拦的 422 响应里同样带 reason。invalid_request 若是某张参考图无法使用造成的,另带 error.input 标出是哪张:image_url、last_image_url 或reference_images[0](下标从 0 开始)。换掉这张图后重新创建任务。
{
"id": "gen_3b9e…",
"type": "video",
"model": "seedance-2.5",
"mode": "t2v",
"status": "succeeded",
"prompt": "A slow dolly shot through a greenhouse",
"params": { "duration": 5, "resolution": "720p", "aspectRatio": null },
"amount": "500000",
"billing": "charged",
"price_version": 3,
"outputs": [
{
"index": 0,
"url": "/v1/generations/gen_3b9e…/outputs/0",
"state": "stored",
"contentType": "video/mp4",
"expiresAt": "2026-11-08T09:14:03.000Z"
}
],
"error": null,
"created_at": "2026-10-09T09:12:41.000Z",
"finished_at": "2026-10-09T09:14:03.000Z"
}生成结束 90 天后,请求内容按数据保留政策清除:prompt 变为空字符串。计费明细(model、params、amount、status 与各时间)保留。
GET /v1/generations/{id}/events 返回任务的时间线,事件码有:created、submitted、generating、delivered、failed_refunded、canceled_refunded、checking、requeued。
{
"data": [
{ "code": "created", "at": "2026-10-09T09:12:41.000Z" },
{ "code": "submitted", "at": "2026-10-09T09:12:43.000Z" },
{ "code": "generating", "at": "2026-10-09T09:12:50.000Z" },
{ "code": "delivered", "at": "2026-10-09T09:14:03.000Z" }
]
}下载产物
outputs 中每一项的 url 形如 /v1/generations/gen_…/outputs/0,是相对 https://api.makernice.com 的路径。带上 API 密钥请求它,响应是 302 跳转到文件的短期链接。需要文件时就重新请求这个地址,不要保存跳转后的链接。
curl -L -o video.mp4 https://api.makernice.com/v1/generations/gen_…/outputs/0 \
-H "Authorization: Bearer $MAKERNICE_API_KEY"- 加上
?download=1让文件以附件形式下载。只在文件已转存(stored)后生效;pending或degraded时跳转到原始文件,此参数不起作用。 curl -L跟随跳转时不会把Authorization头发给跳转后的主机,密钥不会外泄。- 只有
succeeded的任务才有产物,否则返回404 output_not_found。 state:pending(正在保存,链接已可用)、stored(已保存,到expiresAt为止)、degraded(保存没有完成,链接可能更早失效,请尽快下载)、expired(保存的文件已过期,请求返回410 output_expired)、blocked(产物未通过内容审核,url为空串,请联系客服人工处理)。
取消
只有 queued 的任务可以取消,冻结金额全额释放,响应是取消后的任务。任务进入 generating 后无法中止,取消会返回 409 not_cancelable。
curl -X POST https://api.makernice.com/v1/generations/gen_…/cancel \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "id": "gen_…", "status": "canceled", "billing": "released", … }列出生成任务
按创建时间倒序列出账户里的生成任务,包括在控制台创建的。
| 查询参数 | 说明 |
|---|---|
limit | 每页条数,1–100,默认 20。 |
cursor | 上一页返回的 next_cursor。 |
status | 一个或多个状态,用逗号分隔,如 queued,generating。 |
type | video 或 image。 |
model | 模型 id。 |
q | 任务 id 的一部分,或提示词中包含的文字(均不区分大小写)。 |
curl "https://api.makernice.com/v1/generations?status=queued,generating&limit=50" \
-H "Authorization: Bearer $MAKERNICE_API_KEY"
# → { "data": [ …任务… ], "next_cursor": "gen_…" | null, "total": 12 }total 是符合筛选条件的任务总数。next_cursor 为 null 表示已经是最后一页。
Webhook
不想轮询的话,可以让我们在任务进入终态时把任务 POST 到你的服务器。优先发到创建任务时传入的 callback_url;没有传则发到账户的 webhook 地址(在控制台设置里配置)。两者都没有就不推送。
| 事件 | 触发时机 |
|---|---|
generation.succeeded | 任务成功并已扣费。 |
generation.failed | 任务失败,冻结的金额已释放。 |
generation.canceled | 任务已取消,冻结的金额已释放。 |
请求
POST /your/webhook/path HTTP/1.1
Content-Type: application/json
Makernice-Signature: t=1791364443,v1=5f0c2d…e81a
Makernice-Event-Id: evt_1b7f…{
"id": "evt_1b7f…",
"type": "generation.succeeded",
"created_at": "2026-10-09T09:14:03.000Z",
"data": {
"id": "gen_3b9e…",
"status": "succeeded",
"billing": "charged",
"outputs": [
{ "index": 0, "url": "https://api.makernice.com/v1/generations/gen_3b9e…/outputs/0", "state": "stored", … }
],
…
}
}data与GET /v1/generations/{id}返回的任务对象相同,只是outputs[].url是完整地址。Makernice-Event-Id等于正文里的id,重试时保持不变,可用来去重。
验证签名
Makernice-Signature 的格式是 t=<unix 秒>,v1=<hex>,v1 是用你的签名密钥对 t + "." + 原始正文 计算的 HMAC-SHA256。请对收到的原始字节计算、用常量时间比较,并拒绝 t 与本机时间相差超过 5 分钟的请求。签名密钥在控制台设置里查看;callback_url 的推送也用同一个密钥签名。轮换密钥后,尚未发出的推送会改用新密钥签名。
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the body exactly as received (Buffer). Don't re-serialize parsed JSON.
export function verifyWebhook(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => [p.slice(0, p.indexOf("=")), p.slice(p.indexOf("=") + 1)]));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}import hashlib
import hmac
import time
# raw_body: the body exactly as received (bytes). Don't re-serialize parsed JSON.
def verify_webhook(raw_body: bytes, signature_header: str, secret: str, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in signature_header.split(",") if "=" in p)
try:
t = int(parts["t"])
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance_seconds:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts.get("v1", ""))投递与重试
- 只发 HTTPS,不跟随跳转,每次尝试 10 秒超时。
- 返回任意 2xx 即视为送达。请尽快响应,耗时的处理放到之后做。
- 否则依次在 1 min、5 min、30 min、2 h、6 h、12 h 后重试,共 7 次尝试,仍失败则标记为失败。最近的推送记录与最后一次响应可在控制台设置里查看。
- 每个任务通常只推送一个事件,同一次推送可能到达不止一次(比如你那边超时之后)。被内容审核拦下的产物经人工复核放行后,我们会用新的事件 id 再推送一次该任务,所以同一任务可能收到两次通知,以最后一次为准。
模型
GET /v1/models 列出可用的模型及其模式、分辨率和时长,只列出有定价的模式,价格在 modes[].prices 里。不需要 API 密钥,结果可能被缓存最长 60 秒。
resolutions/defaultResolution:resolution的可选值。列表为空表示该模型分辨率固定,不要传resolution。这里列出的分辨率在某些模式下可能没有定价,以modes[].prices为准;对没有定价的组合报价返回422 price_unavailable。durations:视频为以秒计的{ min, max, default },图片为null。modes[].needs:该模式需要哪些图片(image→image_url,lastImage→last_image_url,references→reference_images,最多maxReferences张)。modes[].aspectRatios:aspect_ratio的可选值。modes[].prices:各分辨率的单价。
{
"data": [
{
"id": "seedance-2.5",
"type": "video",
"resolutions": ["480p", "720p", "1080p", "4k"],
"defaultResolution": "720p",
"durations": { "min": 4, "max": 30, "default": 5 },
"modes": [
{
"mode": "t2v",
"needs": { "image": false, "lastImage": false, "references": false },
"maxReferences": null,
"aspectRatios": ["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4", "21:9", "9:21"],
"prices": [{ "resolution": "720p", "unit": "second", "unitPrice": "100000" }]
}
]
}
]
}错误
错误以 JSON 返回:{ "error": { "code", "message", "reason"?, "details"?, "request_id" } }。请按 code 判断,message 的措辞可能调整。每个响应都带 x-request-id 头(与 request_id 相同),联系支持时附上它即可定位到这次请求。
| 状态码 | 错误码 | 怎么处理 |
|---|---|---|
| 400 | invalid_request | 请求体、查询参数或请求头不合法。details 里逐条列出字段和问题,按提示修正。 |
| 401 | invalid_api_key | 密钥缺失、格式不对、已禁用、已过期或已删除。 |
| 402 | insufficient_balance | 给钱包充值。 |
| 402 | key_spend_limit_reached | 调高该密钥的消费上限,或换一把密钥。 |
| 402 | daily_spend_limit_reached | 账户已达到每日消费上限,UTC 0 点重置;如需调高请联系客服。 |
| 403 | org_frozen | 账户已被冻结,请联系客服。 |
| 404 | quote_not_found | 你的账户里没有这个报价,请重新报价。 |
| 404 | task_not_found | 你的账户里没有这个生成任务,检查 id。 |
| 404 | output_not_found | 任务尚未成功,或没有这个序号的产物。 |
| 404 | not_found | 接口不存在,检查请求方法和路径。 |
| 409 | quote_already_used | 重新报价。 |
| 409 | not_cancelable | 只有排队中的任务可以取消。 |
| 410 | quote_expired | 重新报价。 |
| 410 | output_expired | 保存的文件已过期,无法再下载。 |
| 413 | payload_too_large | 请求体超过 64 KB,请缩短提示词或减少图片地址。 |
| 422 | idempotency_conflict | 这个 Idempotency-Key 已用于另一个请求,换一个新的。 |
| 422 | quote_mismatch | 输入改动了影响价格的参数(模型、模式、分辨率或时长),请用新的输入重新报价。 |
| 422 | price_unavailable | 这个组合没有定价,请选 GET /v1/models 里列出的组合。 |
| 422 | content_rejected | 未通过内容审核或模型方的安全策略;error.reason 说明被拦的是什么(prompt、input_image 或 model)。修改提示词或参考图后重试;本次未扣费。 |
| 429 | rate_limited | 等待 Retry-After 指定的秒数后重试。 |
| 429 | concurrency_limit_reached | 账户同时进行中的任务已达上限。等任一任务结束后再提交(参考 Retry-After),或联系客服调高上限。 |
| 500 | internal_error | 服务端意外错误。请退避后重试;联系客服时附上 request_id。 |
遇到意外的 5xx 或网络错误,退避后重试。重试创建任务时沿用同一个 Idempotency-Key,就不会被重复扣费。
频率限制
限额按分钟计。超出后返回 429 rate_limited,并带 Retry-After 头(秒)。
| 范围 | 限额 |
|---|---|
| 每把密钥 · 写操作(报价、创建、取消) | 60 次 / 分钟 |
| 每把密钥 · 读操作(查询、列表、时间线、产物) | 600 次 / 分钟 |
| 每个账户 · 所有密钥的写操作合计 | 120 次 / 分钟 |
| 每个 IP · 模型、价格接口,以及密钥被拒的请求 | 120 次 / 分钟 |
此外,每把密钥同时进行中的创建请求最多 4 个,超出同样返回 429。限额可能调整。
每个账户另有两项用量上限,默认同时进行中的生成任务 2 个、每个 UTC 自然日消费 100 美元。超出时分别返回 429 concurrency_limit_reached(带 Retry-After)或 402 daily_spend_limit_reached。如需调高请联系客服。
幂等
POST /v1/generations必须带Idempotency-Key请求头:1–200 个可打印 ASCII 字符,不含空格。每个新任务用一个新值,比如 UUID。- 该值在账户内唯一,账户下的所有 API 密钥共用同一个范围。
- 用同一个值、同一个报价和输入重复请求,返回原来的任务(
200),不会再次扣费。 - 同一个值配上不同的
quote_id、不同的input(包括提示词和图片地址)或不同的callback_url,返回422 idempotency_conflict。 - 报价和取消不需要 Idempotency-Key。重复取消一个已取消的任务会返回
409 not_cancelable,可以用GET查看状态。