用 Python 调用 Makernice API
用 Python 的 requests 库调用 Makernice:先报精确价格,再创建生成、轮询到完成,最后下载文件。一共四次调用,不需要安装 SDK。
开始之前
- 在控制台创建 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)}")代码做了什么
- 报价:
POST /v1/generations/quote返回精确金额(micro-USD 整数字符串),锁定 12 分钟,这一步不扣费。 - 创建:
POST /v1/generations带上quote_id、同一份输入和Idempotency-Key请求头。用同一个键重试,拿回的是同一个生成,不会重复下单。 - 等待:轮询
GET /v1/generations/{id}直到succeeded、failed或canceled;或者传callback_url,完成后我们把结果 POST 到你的服务器。 - 下载:每个
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())里也能直接用。
生成失败要付钱吗?
不用。创建时冻结金额,失败或取消就全额释放;生成完成后被内容审核拦截的仍会扣费。详见失败不扣费。