多模态 API 快速开始
用自己的 API Key 完成图片、视频、音乐的询价、创建、重放、轮询、下载和收费核对
多模态 API 快速开始
图片、视频和音乐都可以通过 https://api.uniroute.app 调用,使用与 LLM 相同的统一 API Key。一次任务按 USD 钱包实际结算,也会消费该 Key 的共享限额。
先在 Key 管理 创建或选择有效的统一 Key:开启自动渠道,不绑定 Group,不设置渠道限制。账户须有可用 USD 钱包资金,Key 的总额度和时间窗口额度也须足够。已有 Key 可以继续使用,不需要为了重跑教程创建新 Key、重置额度或重新充值。详见 鉴权与权限。
用独立 Python 示例跑完整流程
需要 macOS、Linux 或 WSL 上的 Python 3.9 或更新版本和 curl,无第三方 Python 依赖。客户端使用系统文件锁,不支持原生 Windows Python。将 Key 存入只有自己能读取的文件,例如 ~/.config/uniroute/api-key,不要把 Key 写进命令、公开日志或代码仓库。若已经提供了 Key 文件,请直接使用该路径。
chmod 600 ~/.config/uniroute/api-key
curl --fail --proto '=https' --proto-redir '=https' \
https://uniroute.app/examples/uniroute-media.py -o uniroute-media.py
python3 uniroute-media.py \
--key-file ~/.config/uniroute/api-key \
--budget-usd 0.5 \
--state ./media-run.json \
--output ./media-output \
--kind all \
--verify-idempotency
--budget-usd 是这次运行所选任务的合计上限,请填写自己剩余可消费预算内的金额。客户端只对选中的每种媒体进行一次新询价,合计报价超过上限时不创建任何任务。报价合计仅说明本次任务的预计费用,不代表钱包充值或新增 Key 额度。
默认 --kind all 依次运行图片、视频、音乐。可改成 image、video 或 music。示例会检查当前目录中的模型、选项和供应商,保存精确报价为 max_cost_usd,使用同一幂等键及相同请求体重放一次,确认任务 ID 不变,再轮询直到生成成功且账单 charged,下载所有输出,读取本 Key 历史及实际收费增量。
| 类型 | 示例模型 | 选项 | 示例单次报价 USD |
|---|---|---|---|
| 图片 | z-image | 16:9 | 0.0056 |
| 视频 | bytedance/seedance-1.5-pro | 480p:5 | 0.05 |
| 音乐 | ai-music-api/generate | acoustic, folk, warm | 0.084 |
这些是示例价格,合计 0.1396;模型、选项和价格都可能改变,每次以自己的 /v1/media/quote 响应为准。客户端不会在推荐模型下线时盲选替代模型。
程序只输出任务 ID、文件名及费用,不输出 Key。单个下载最多 64 MiB,总下载最多 128 MiB。下载只走解析结果全部为公网 IPv4/IPv6 地址的 HTTPS URL,连接固定到已验证的地址并验证原域名证书,不携带 Bearer;遇到重定向会停下,保留任务 ID 供检查。输出文件名由任务 ID 生成,不采用远端文件名。
中断后如何恢复
保存 media-run.json 和已下载文件。使用同一命令、同一 Key、同一预算、同一状态文件重跑。已知任务 ID 仅查询原任务,不重新询价、创建或 POST 重放;已完成任务不重新消费。默认每个任务最多等 1800 秒,可以用 --wait-seconds 3600 延长。
如果创建请求中断且没拿到任务 ID,程序会记录未知状态并停止。不要删除状态文件,也不要用新幂等键重建。保留原请求和幂等键,查询本 Key 历史或联系支持确认是否已受理。released_unknown 同样不代表一定没有上游费用。详见 计费与恢复。客户端在读取状态前取得该状态文件的非阻塞独占锁,覆盖全部调用和下载;另一个进程会直接停止,不会另建任务。不要使用多个不同状态文件并发消费同一份预算,客户端无法合并不同运行的预算。
用 curl 查看真实接口
下面需要 Bash、curl、jq 和 Python 3。在同一个 Bash 会话按顺序执行;set -euo pipefail 让询价、创建或 JSON 检查失败时立即停止,不能跳过失败步骤继续重放。认证头保存在临时私有文件里,避免把原始 Key 放进 curl 参数。临时认证文件可清理;请求体、幂等键和任务 ID 必须保留用于恢复。
set -euo pipefail
umask 077
KEY_FILE="$HOME/.config/uniroute/api-key"
BASE='https://api.uniroute.app'
AUTH_HEADER="$(mktemp)"
printf 'Authorization: Bearer %s\n' "$(cat "$KEY_FILE")" > "$AUTH_HEADER"
trap 'rm -f "$AUTH_HEADER"' EXIT
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
"$BASE/v1/media/catalog" -o catalog.json
jq '.data | {enabled, currency, policy_version, models: [.models[] | {modelId, mediaKind, provider, unit, param, input_fields}]}' catalog.json
目录使用 camelCase 字段 modelId、mediaKind,以及 param.options[].value。创建使用 snake_case 字段 media_type,模型参数 option 必须是目录中的值。部分模型带 input_fields,可能需要外部图片等输入;本教程选用不需要外部引用的模型。目录单位价不能直接作为总价,视频还可能按秒计费。
询价,再保存精确请求
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
-H 'Content-Type: application/json' \
-d '{"model":"z-image","option":"16:9"}' \
"$BASE/v1/media/quote" -o quote.json
jq '.data' quote.json
jq -e '.code == 0 and .data.currency == "USD" and .data.policy_version == "usd-wallet-v1" and (.data.quoted_usd | type == "string")' quote.json
jq '.data | {media_type:"image", model:.model, option:.option, prompt:"A quiet lake at sunrise, watercolor illustration.", max_cost_usd:.quoted_usd}' quote.json > request.json
python3 -c 'import uuid; print(uuid.uuid4())' > idempotency-key.txt
在继续之前检查报价、模型、选项及自己的剩余预算。quoted_usd 和 max_cost_usd 都是精确十进制字符串,不应转为浮点数或按目录单位价格估算。批量任务需先加总所有报价,不能只检查每个任务分别小于预算。上面的手动 curl 示例只创建一张图。
创建与相同请求重放
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(cat idempotency-key.txt)" \
--data-binary @request.json "$BASE/v1/media/tasks" -o created.json
jq -e '.code == 0 and (.data.task_id | type == "string")' created.json
jq -r '.data.task_id' created.json > task-id.txt
# 创建和重放均返回 HTTP 202;至少间隔 7 秒,创建重放也占创建限流。
sleep 7
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: $(cat idempotency-key.txt)" \
--data-binary @request.json "$BASE/v1/media/tasks" -o replay.json
jq -e --arg id "$(cat task-id.txt)" '.data.task_id == $id' replay.json
媒体接口返回 {code:0,message:"...",data:{...}}。同一 Key、同一幂等键、完全相同请求体应返回同一个任务 ID;同键不同体会冲突。POST 出错或超时时,停止这个 curl 流程,不要自动重试,也不要换幂等键。
轮询、下载与核对
# 尚未终态时每隔 5 秒重复这条 GET,始终查询保存的同一个任务 ID。
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
"$BASE/v1/media/tasks/$(cat task-id.txt)" -o task.json
jq '.data | {task_id, status, billing, urls}' task.json
jq -e '.data.status == "success" and .data.billing.state == "charged"' task.json
# 只有上一步检查成功后才下载;不要在下载命令中传 API 认证头。
URL="$(jq -r '.data.urls[0].url' task.json)"
curl --fail --location --proto '=https' --proto-redir '=https' \
"$URL" -o image-output.png
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
"$BASE/v1/media/tasks?limit=20" -o history.json
jq '.data | {tasks, next_cursor}' history.json
curl --fail-with-body --proto '=https' -H @"$AUTH_HEADER" \
"$BASE/v1/usage" -o usage.json
jq '{mode, quota, usage}' usage.json
urls 是对象数组,例如 [{"url":"https://...","kind":"image"}];音乐可能有多个输出,请全部下载。历史翻页使用返回的 next_cursor:curl ... --get --data-urlencode "limit=20" --data-urlencode "cursor=$CURSOR" "$BASE/v1/media/tasks"。
/v1/usage 是原始 JSON,没有 data 包装。限额 Key 读取 quota.used;没有总限额的 Key 读取 usage.total.actual_cost。usage.total.cost 是费率前参考金额,不能代替实际扣费。钱包余额以 Console 为准。完整示例会输出每个任务的 charged_usd 与用量增量;同 Key 其他并发调用也会进入用量增量。
UniRoute