Makernice

用 Python 调用 Makernice API

用 Python 的 requests 库调用 Makernice:先报精确价格,再创建生成、轮询到完成,最后下载文件。一共四次调用,不需要安装 SDK。

Makernice 团队 ·

开始之前

  • 在控制台创建 API Key,设成环境变量 MAKERNICE_API_KEY。
  • Python 3,并执行 pip install requests。

完整示例

quickstart.py
import os
import time
import uuid
from decimal import Decimal

import requests

API = "https://api.makernice.com/v1"
ORIGIN = "https://api.makernice.com"
HEADERS = {"Authorization": f"Bearer {os.environ['MAKERNICE_API_KEY']}"}
TIMEOUT_SECONDS = 30
POLL_INTERVAL_SECONDS = 5
MAX_WAIT_SECONDS = 30 * 60
FINAL = {"succeeded", "failed", "canceled"}


def call(method, path, headers=None, **kwargs):
    # Waits out 429 (rate_limited, concurrency_limit_reached) using Retry-After; raises on any other error.
    for _ in range(5):
        r = requests.request(method, API + path, headers={**HEADERS, **(headers or {})}, timeout=TIMEOUT_SECONDS, **kwargs)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "5")))
            continue
        if not r.ok:
            raise RuntimeError(f"{method} {path} -> {r.status_code}: {r.text}")
        return r.json()
    raise RuntimeError(f"{method} {path}: still rate limited")


job = {
    "model": "seedance-2.5",
    "mode": "t2v",
    "prompt": "A slow dolly shot through a greenhouse",
    "duration": 5,
    "resolution": "720p",
}

# 1. Quote: the exact amount, locked for 12 minutes
quote = call("POST", "/generations/quote", json=job)
usd = Decimal(quote["amount"]).scaleb(-6)  # micro-USD integer string -> dollars, no float rounding
print(f"Quoted ${usd} (price list v{quote['price_version']}), valid until {quote['expires_at']}")

# 2. Create: same input + quote_id. A retry with the same Idempotency-Key returns the same generation
idempotency_key = str(uuid.uuid4())
gen = call("POST", "/generations", json={"quote_id": quote["id"], "input": job}, headers={"Idempotency-Key": idempotency_key})

# 3. Poll: queued -> generating -> moderating -> succeeded | failed | canceled (checking: keep polling)
deadline = time.monotonic() + MAX_WAIT_SECONDS
while gen["status"] not in FINAL:
    if time.monotonic() > deadline:
        raise SystemExit(f"{gen['id']} is still {gen['status']}; poll it again later")
    time.sleep(POLL_INTERVAL_SECONDS)
    gen = call("GET", f"/generations/{gen['id']}")

if gen["status"] != "succeeded":
    # billing is "released": the hold went back to your balance
    raise SystemExit(f"{gen['status']}, billing {gen['billing']}: {gen['error']}")

# 4. Download: outputs[].url is relative to the API origin and redirects (302) to the file
output = gen["outputs"][0]
if output["state"] == "blocked":
    raise SystemExit("Output blocked by moderation (the run is charged); contact support for a manual review")
r = requests.get(ORIGIN + output["url"], headers=HEADERS, timeout=120)  # requests drops Authorization on the redirect
r.raise_for_status()
with open("output.mp4", "wb") as f:
    f.write(r.content)
print(f"Saved output.mp4, charged ${Decimal(gen['amount']).scaleb(-6)}")

代码做了什么

  1. 报价:POST /v1/generations/quote 返回精确金额(micro-USD 整数字符串),锁定 12 分钟,这一步不扣费。
  2. 创建:POST /v1/generations 带上 quote_id、同一份输入和 Idempotency-Key 请求头。用同一个键重试,拿回的是同一个生成,不会重复下单。
  3. 等待:轮询 GET /v1/generations/{id} 直到 succeeded、failed 或 canceled;或者传 callback_url,完成后我们把结果 POST 到你的服务器。
  4. 下载:每个 outputs[].url 返回 302,跳到带签名的文件地址。

Python 要注意的地方

  • 金额是 micro-USD 整数字符串,示例用 Decimal 换算,不用 float,不会有舍入误差。
  • 429 响应带 Retry-After,call() 会按它等待,最多重试 5 次。
  • requests 跟随跨主机的下载跳转时会去掉 Authorization 请求头,这正是想要的:签名文件地址不需要你的 Key。

常见问题

有官方 SDK 吗?

没有。REST API 只有四次调用,本页代码就是完整的客户端。想自动生成客户端,可以用 /openapi.json 的 OpenAPI 描述。

该轮询还是用 webhook?

脚本和批量任务用轮询就够了。Web 应用建议传 callback_url 并验证签名:FastAPI 接入里有完整的接收端,同一个 verify_webhook 函数在 Django(request.body)和 Flask(request.get_data())里也能直接用。

生成失败要付钱吗?

不用。创建时冻结金额,失败或取消就全额释放;生成完成后被内容审核拦截的仍会扣费。详见失败不扣费。

相关链接