教程
在 hiapi 上用单一统一 API 构建多模型 AI 应用
- 模型
- seedance-2.5
你将构建什么
大多数团队最终会为一个产品调用三四个不同的 AI 提供商:用文生图模型做缩略图,用文生视频模型做社交短视频,也许还要用 TTS 模型做旁白。每个提供商都有各自的 SDK、各自的认证方案、各自的轮询逻辑。hiapi 把这一切收拢为一种端点模式:POST /v1/tasks 创建任务,GET /v1/tasks/:id 查询任务,平台上的每个生成模型都适用。
本教程用三个真实且当前在线的模型演示这套集成模式——z-image(文生图)、qwen-image-2.0(文生图,schema 不同)以及 seedance-2.5(文生视频)——让你看清哪些地方 API 完全一致、哪些地方只是每个模型的 input 对象不同。
前置条件
- 从控制台获取的 hiapi API key
curl或任何能发送 JSON 的 HTTP 客户端- 无需 SDK——下面每个示例都是纯 HTTP 调用
每个请求都需要一个请求头:
Authorization: Bearer <your-api-key>
key 无效或缺失时,会在触及任何模型之前返回 401 类错误:
{
"error": {
"code": "permission_denied",
"message": "invalid api key",
"request_id": "req_...",
"type": "hiapi_error"
}
}
不存在无密钥或匿名模式——每次 /v1/tasks 调用都必须携带有效的 bearer token。密钥轮换与作用域请参见认证。
最小可运行示例
1. 创建任务
创建调用的结构始终相同:一个 model id 和一个模型专属的 input 对象。下面是用 z-image 创建图片任务:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-image",
"input": {
"prompt": "a minimalist workspace, soft morning light, isometric illustration",
"aspect_ratio": "16:9"
}
}'
响应会立即返回——生成是异步进行的:
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "queued"
}
}
现在用 qwen-image-2.0 走同样的模式。同样的端点、同样的外层结构——只有 input 不同(该模型接收 size 字符串,而不是 aspect_ratio):
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen-image-2.0",
"input": {
"prompt": "a minimalist workspace, soft morning light, isometric illustration",
"size": "2048*2048"
}
}'
再用 seedance-2.5/text-to-video 创建一个视频任务——仍是同样的外层结构,现在多了 duration 和取值更宽的 aspect_ratio 枚举:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedance-2.5/text-to-video",
"input": {
"prompt": "a paper airplane gliding through a sunlit office, slow motion",
"duration": 5,
"aspect_ratio": "16:9"
}
}'
这就是核心要点:任务的外层结构从不改变,只有 input 会变。 hiapi 上每个模型都在其模型页面上记录了自己的 input schema——在构建新的集成之前务必查看具体模型的 schema,因为 aspect_ratio、size 和 duration 这类字段在不同模型之间不可互换。
2. 轮询结果
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxxxxxxxxxx \
-H "Authorization: Bearer $HIAPI_API_KEY"
status 会依次经过 queued → handling → archiving,最终进入 success 或 fail 终态。成功时,data.output 是一个数组——大多数图片/视频模型返回一个条目,但单个任务可以返回多种输出类型(例如视频模型同时返回 video 和 thumbnail):
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "success",
"output": [
{ "type": "image", "url": "https://...", "expireAt": "2026-10-09T00:00:00Z" }
]
}
}
expireAt 很重要——输出 URL 默认是临时的。任务一完成就下载字节内容(或重新上传到自己的存储)。如果需要平台更长期地保留文件,可在创建时设置 storage: "persistent" 或事后升级——参见存储。
失败时,你会得到 error 对象而不是 output:
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "fail",
"error": { "code": "...", "message": "..." }
}
}
下面这个最小的 Python 轮询循环对上面所有模型都同样适用:
import time
import requests
def run_task(model: str, input_payload: dict, api_key: str) -> list[dict]:
headers = {"Authorization": f"Bearer {api_key}"}
create = requests.post(
"https://api.hiapi.ai/v1/tasks",
headers=headers,
json={"model": model, "input": input_payload},
).json()
task_id = create["data"]["taskId"]
time.sleep(3) # initial wait before first poll
while True:
poll = requests.get(
f"https://api.hiapi.ai/v1/tasks/{task_id}", headers=headers
).json()
status = poll["data"]["status"]
if status == "success":
return poll["data"]["output"]
if status == "fail":
raise RuntimeError(poll["data"]["error"])
time.sleep(4) # 3-5s between polls; use 5-10s for video models
生产环境模式
在生产环境中使用回调代替轮询
轮询对于脚本或 CLI 工具没问题,但在任何真实流量规模下,更推荐使用回调:创建任务时传入 callback 对象,hiapi 会把终态结果 POST 到你的端点,而不是让你在循环里反复请求 GET /v1/tasks/:id。
{
"model": "z-image",
"input": { "prompt": "...", "aspect_ratio": "1:1" },
"callback": { "url": "https://yourapp.com/webhooks/hiapi", "when": "final" }
}
when: "final" 意味着你只会收到一次 POST,在终态(success 或 fail)时触发——没有中间 handling/archiving 状态带来的噪音。如果你已配置了回调却什么都没收到,请检查你的端点是否可公开访问并快速返回 2xx;关于回调静默丢失最常见的原因,参见这篇相关文章。
幂等重试
网络抖动在所难免。如果在超时后重试 POST /v1/tasks 调用而没有保护措施,你可能会创建(并被计费)重复任务。发送一个 Idempotency-Key 请求头(任意不超过 255 字节的字符串,通常是每个逻辑请求生成的 UUID),同一 key 的重试会返回原始任务而不是创建新任务:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Idempotency-Key: 8f14e45f-ceea-467e-bd5f-...-9a2c" \
-H "Content-Type: application/json" \
-d '{"model": "z-image", "input": {"prompt": "...", "aspect_ratio": "1:1"}}'
按模型处理校验错误
由于每个模型都有自己的 input schema,最常见的失败模式是对某个字段返回 400——该字段对某模型有效、对另一模型无效,例如把 aspect_ratio 发给期望 size 的模型:
{
"code": 400,
"data": null,
"error_code": "INVALID_REQUEST",
"message": "invalid input: aspect_ratio: value must be one of '1:1', '4:3', '3:4', '16:9', '9:16'"
}
如果你在构建多模型路由器(让用户或你自己的逻辑在运行时选择模型),请维护一个小的按模型输入构建器注册表,而不是一个共享的 payload 结构:
MODEL_INPUT_BUILDERS = {
"z-image": lambda prompt, **kw: {
"prompt": prompt, "aspect_ratio": kw.get("aspect_ratio", "1:1")
},
"qwen-image-2.0": lambda prompt, **kw: {
"prompt": prompt, "size": kw.get("size", "2048*2048")
},
"seedance-2.5/text-to-video": lambda prompt, **kw: {
"prompt": prompt,
"duration": kw.get("duration", 5),
"aspect_ratio": kw.get("aspect_ratio", "16:9"),
},
}
def build_task_payload(model: str, prompt: str, **kw) -> dict:
return {"model": model, "input": MODEL_INPUT_BUILDERS[model](prompt, **kw)}
这样就把每个模型的特殊之处集中在一处,而不是散落在每个调用点。不存在或跨账号的 taskId 会返回普通的 404:
{ "code": 404, "message": "task not found", "data": null }
而临时的 503 应使用指数退避重试,而不是直接暴露给用户——速率限制和容量限制记录在速率限制中。
相关阅读
- 异步任务 API 参考——完整的
create/detail契约 - 认证——API key 作用域与轮换
- 存储——将输出保留到默认过期时间之后
- 为什么你的任务回调可能没有触发
- 浏览所有模型——每个模型自己的
inputschema 都在其页面上 - 定价——成本按模型和输出计算,而非按请求统一计价
常见问题
每个模型都需要不同的 SDK 或 API key 吗?
不需要。一个 API key、一种端点模式(/v1/tasks),适用于平台上的每个模型。只有请求体中的 input 对象会变。
为什么刚创建完任务,响应里还没有 output?
生成是异步的。创建调用只返回 taskId 和 status: "queued"——你必须轮询 GET /v1/tasks/:id(或使用回调),直到 status 达到 success 或 fail。
可以为了快速测试而不带认证调用 API 吗?
不可以——每次 /v1/tasks 调用都需要有效的 Authorization: Bearer key。生产调用没有无密钥或演示模式;请先从控制台生成 key。
如果在输出 URL 过期后轮询会怎样?
任务记录本身会保留,但非持久化任务的 output[].url 在 expireAt 之后将不再可解析。成功时请立即下载文件,或者如果需要长期保留,在创建时设置 storage: "persistent"。
如何知道某个模型接受哪些字段?
查看模型下该模型的页面——每个都记录了其确切的 input schema。不要假设 aspect_ratio、size 或 duration 可以在模型之间通用;如上所示,z-image 和 qwen-image-2.0 在如何表达图片形状上就已经不一致了。