接入文档

OpenAI 兼容。把官方 SDK 的 base_url 指到本站即可。

鉴权

在控制台创建虚拟密钥,请求头携带 Authorization: Bearer sk-t365-...

curl https://xuanwujicloud.com/v1/models \
  -H "Authorization: Bearer sk-t365-..."

返回约定

成功时 HTTP 状态码为 200,响应体为 JSON(流式对话除外,见下方 SSE)。失败时同样返回 JSON,结构统一为:

{
  "error": {
    "message": "余额不足,请先充值",
    "type": "billing_error",
    "code": "insufficient_balance"
  }
}
字段说明
error.message可读错误说明
error.typeinvalid_request_error / auth_error / billing_error / server_error / upstream_error
error.code可选机器码,如 missing_tokeninvalid_api_keyaccount_disabledinsufficient_balancebudget_exceededmodel_not_foundmodel_not_allowedno_deploymenttask_not_found

常见 HTTP 状态码:400 请求不合法,401 缺少或无效密钥,403 账号停用 / 余额不足 / 超预算 / 无权调用该模型,404 模型或任务不存在,502 上游失败,503 模型暂无可用上游。

对话 Completions

与 OpenAI POST /v1/chat/completions 一致,支持 stream。模型请使用广场中的 model_id,也可用短名(如 deepseek-chat)。

请求
curl https://xuanwujicloud.com/v1/chat/completions \
  -H "Authorization: Bearer sk-t365-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen/qwen-plus",
    "messages": [{"role":"user","content":"用一句话介绍 OpenRouter"}]
  }'
成功返回(非流式)

主体与 OpenAI 一致,额外附带本站结算字段 billmodel 会被改写为广场中的 model_id

{
  "id": "chatcmpl-xxxxxxxx",
  "object": "chat.completion",
  "created": 1710000000,
  "model": "qwen/qwen-plus",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "OpenRouter 是一个统一接入多家大模型的 API 网关。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 24,
    "total_tokens": 42
  },
  "bill": {
    "amount": 0.0012,
    "bill_amount": 0.0012,
    "estimate": 0.008,
    "currency": "CNY",
    "prompt_tokens": 18,
    "completion_tokens": 24,
    "cached_tokens": 0,
    "markup": 1.0,
    "billed": 1
  }
}
字段说明
choices[].message.content模型回复文本
usage上游 Token 用量,结算以此为准
bill.amount / bill.bill_amount本次实扣 = 后台目录价 × 该用户 markup
bill.estimate请求时预扣金额
bill.markup该密钥所属账号的价格系数
bill.cached_tokens命中缓存的输入 Token
bill.billed1 表示已记账

流式 stream: true

响应 Content-Typetext/event-stream,帧格式与 OpenAI SSE 一致,服务端记账但不改写 SSE 帧,因此流式响应里没有 bill 字段。客户端按行解析 data:,直到 data: [DONE]

data: {"id":"chatcmpl-xxxxxxxx","object":"chat.completion.chunk","model":"qwen/qwen-plus","choices":[{"index":0,"delta":{"content":"Open"},"finish_reason":null}]}

data: {"id":"chatcmpl-xxxxxxxx","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Router"},"finish_reason":null}]}

data: [DONE]

向量 Embeddings

请求
curl https://xuanwujicloud.com/v1/embeddings \
  -H "Authorization: Bearer sk-t365-..." \
  -H "Content-Type: application/json" \
  -d '{"model":"qwen/text-embedding-v3","input":"你好,世界"}'
成功返回

与 OpenAI /v1/embeddings 一致,额外附带 bill(字段含义同对话)。

{
  "object": "list",
  "model": "qwen/text-embedding-v3",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [0.0123, -0.0456, 0.0789]
    }
  ],
  "usage": {
    "prompt_tokens": 4,
    "total_tokens": 4
  },
  "bill": {
    "amount": 0.0001,
    "estimate": 0.0004,
    "currency": "CNY",
    "prompt_tokens": 4,
    "completion_tokens": 0,
    "cached_tokens": 0,
    "billed": 1
  }
}

data[].embedding 为浮点数组,长度由模型决定。上面只截取了前几维作为示意。

文生图

POST /v1/images/generations。同步完成时直接返回 image_url;异步任务返回 id / task_id(同一值)后请轮询查询接口。可选 provider 指定供应商(如 dashscope / moma)。

请求
curl https://xuanwujicloud.com/v1/images/generations \
  -H "Authorization: Bearer sk-t365-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moma/qwen-image-2.0-pro",
    "provider": "moma",
    "prompt": "写实风格,黄昏海边礁石,暖金色阳光",
    "size": "1024*1024"
  }'
异步提交返回(需轮询)
{
  "id": "img-xxxxxxxx",
  "task_id": "img-xxxxxxxx",
  "object": "task",
  "kind": "image",
  "status": "queued",
  "estimate": 0.15,
  "markup": 1.0,
  "currency": "CNY"
}
同步完成返回
{
  "id": "img-xxxxxxxx",
  "task_id": "img-xxxxxxxx",
  "object": "task",
  "kind": "image",
  "status": "succeeded",
  "image_url": "https://cdn.example.com/generated/xxxx.png",
  "bill_amount": 0.12,
  "estimate": 0.15,
  "markup": 1.0,
  "currency": "CNY"
}

图生图时在请求里传 imageskind: "i2i",成功报文里 kindi2i,其余字段相同。拿到 id / task_idstatus 不是终态时,用 查询生成结果 轮询。

文生视频 / 图生视频

POST /v1/videos/generations。文生视频与图生视频都是异步任务:提交成功不会立刻返回成片地址,只会返回任务编号 id / task_id(两者相同)。把这个值填进 GET /v1/tasks/{task_id} 轮询。HappyHorse 可传 resolution(720p/1080p)、duration(秒)、ratio

请求
curl https://xuanwujicloud.com/v1/videos/generations \
  -H "Authorization: Bearer sk-t365-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moma/happyhorse-1.1-t2v",
    "provider": "moma",
    "prompt": "黄昏海边,镜头缓缓前推,浪花拍上礁石",
    "resolution": "720p",
    "duration": 5,
    "ratio": "16:9"
  }'
成功返回(文生视频 / 图生视频相同)
{
  "id": "vid-xxxxxxxx",
  "task_id": "vid-xxxxxxxx",
  "object": "task",
  "kind": "video",
  "status": "queued",
  "estimate": 0.8,
  "markup": 1.0,
  "currency": "CNY"
}

查询用的就是这里的 idtask_id,例如 GET /v1/tasks/vid-xxxxxxxxvideo_url 不会出现在提交接口,需轮询到 statussucceeded 后读取。

图生视频请求

同一接口,在 content 里带参考图(或传 images)。返回字段与文生视频完全一致,同样用 task_id 查询。

curl https://xuanwujicloud.com/v1/videos/generations \
  -H "Authorization: Bearer sk-t365-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "moma/happyhorse-1.1-i2v",
    "provider": "moma",
    "prompt": "镜头缓缓前推,浪花拍上礁石",
    "resolution": "720p",
    "duration": 5,
    "content": [
      {"type": "text", "text": "镜头缓缓前推,浪花拍上礁石"},
      {"type": "image_url", "image_url": {"url": "https://cdn.example.com/ref.jpg"}, "role": "reference_image"}
    ]
  }'

查询生成结果

task_id 来自提交文生图 / 文生视频 / 图生视频后的返回字段 idtask_id(同一值)。用它轮询 GET /v1/tasks/{task_id},直到 statussucceededfailed。成功时图片看 image_url,视频看 video_url。实扣金额在 bill_amount(已含账号价格系数)。

请求
curl https://xuanwujicloud.com/v1/tasks/TASK_ID \
  -H "Authorization: Bearer sk-t365-..."
进行中
{
  "id": "img-xxxxxxxx",
  "task_id": "img-xxxxxxxx",
  "object": "task",
  "kind": "image",
  "status": "running",
  "image_url": "",
  "video_url": "",
  "error_msg": "",
  "bill_amount": 0,
  "estimate_amount": 0.15,
  "billed": 0,
  "balance": 12.34,
  "frozen": 0.15,
  "markup": 1.0,
  "currency": "CNY"
}
成功
{
  "id": "img-xxxxxxxx",
  "task_id": "img-xxxxxxxx",
  "object": "task",
  "kind": "image",
  "status": "succeeded",
  "image_url": "https://cdn.example.com/generated/xxxx.png",
  "video_url": "",
  "error_msg": "",
  "bill_amount": 0.12,
  "estimate_amount": 0.15,
  "billed": 1,
  "balance": 12.22,
  "frozen": 0,
  "markup": 1.0,
  "currency": "CNY"
}
失败
{
  "id": "vid-xxxxxxxx",
  "task_id": "vid-xxxxxxxx",
  "object": "task",
  "kind": "video",
  "status": "failed",
  "image_url": "",
  "video_url": "",
  "error_msg": "生成失败,请稍后重试。",
  "bill_amount": 0,
  "estimate_amount": 0.8,
  "billed": 0,
  "balance": 12.34,
  "frozen": 0,
  "markup": 1.0,
  "currency": "CNY"
}
字段说明
id / task_id任务编号,二者相同,取提交接口返回的这个值填进路径
kindimage / i2i / video
statusqueued 排队、running 生成中、succeeded 完成、failed 失败,以及 expired / cancelled
image_url图片任务成功后的地址;视频任务为空字符串
video_url视频任务成功后的地址;图片任务为空字符串
error_msg失败原因,成功时为空
bill_amount实扣金额;未结算时为 0
estimate_amount提交时预扣金额
billed1 已结算,0 尚未结算
balance / frozen当前可用余额与冻结金额
markup该密钥所属账号的价格系数
import time, requests

headers = {"Authorization": "Bearer sk-t365-..."}
base = "https://xuanwujicloud.com/v1"

created = requests.post(base + "/images/generations", headers=headers, json={
    "model": "moma/qwen-image-2.0-pro",
    "prompt": "一只橘猫坐在窗边",
    "size": "1024*1024",
}).json()

if created.get("error"):
    print(created["error"]["message"])
elif created.get("status") == "succeeded":
    print(created.get("image_url"))
else:
    task_id = created.get("task_id") or created.get("id")
    while True:
        task = requests.get(base + "/tasks/" + task_id, headers=headers).json()
        if task.get("status") in ("succeeded", "failed", "expired", "cancelled"):
            print(task.get("status"), task.get("image_url") or task.get("video_url") or task.get("error_msg"))
            break
        time.sleep(3)

常见 statusqueued(排队)、running(生成中)、succeeded(完成)、failed(失败)。任务不属于当前密钥所属账号时返回 404error.codetask_not_found

模型列表

GET /v1/models 返回当前已启用且配置了上游部署的模型。pricing 已乘以当前密钥所属账号的价格系数 markup

成功返回
{
  "object": "list",
  "data": [
    {
      "id": "qwen/qwen-plus",
      "object": "model",
      "created": 0,
      "owned_by": "dashscope",
      "mode": "chat",
      "context_length": 131072,
      "pricing": {
        "prompt": 0.0008,
        "completion": 0.002,
        "cached": 0.0002,
        "markup": 1.0,
        "currency": "CNY"
      }
    }
  ]
}
字段说明
data[].id广场 model_id,请求时填到 model
data[].modechat / embedding / image / video 等,需与对应接口匹配
data[].pricing.prompt输入单价(元 / 千 Token 或目录单位,已含系数)
data[].pricing.completion输出单价
data[].pricing.cached缓存命中单价

计费说明

用户实付 = 后台维护的目录价 × 该账号价格系数(markup,默认 1)。管理员可在后台为每个用户单独设置倍率。调用接口返回的 bill.amount / bill.bill_amount、任务的 bill_amount,以及 GET /v1/models 里的 pricing,都已按该系数计算。网站「定价」页展示的是未乘用户倍率的目录价。

对话 / 向量:先按预估 Token 预扣,成功后按实际上游 usage 结算,差额退回或补扣,失败全额解冻。响应中附带 bill 字段(流式接口在服务端记账,不改 SSE 帧格式)。

文生图 / 文生视频:提交时预扣(含 15% 缓冲),完成后按实际张数或时长结算,bill_amount 为乘以价格系数后的实扣。