OneKeyToken 开发者接入指南
版本:2026-09-04
状态:已同步到网站 API 文档、应用内开发文档和后端 docs service
适用对象:开发者、后端工程师、AI 编程助手、自动化接入 Agent
生产 API Base URL:https://my.onekeytoken.com/v1
本文以当前网关代码和生产域名为准。后续改动请先更新本 Markdown 源,再同步官网文档、应用内文档和后端 docs service。
1. 给 AI 的快速接入指令
如果你是 AI 编程助手,请按下面的契约接入 OneKeyToken:
- 常规对话使用 OpenAI Chat Completions 兼容接口;受管代码解释器、联网搜索和网页抓取使用 OpenAI Responses 兼容接口;实时与文件语音转写使用专用 ASR 接口。
- 将 SDK 的
base_url/baseURL设置为https://my.onekeytoken.com/v1。 - 将 SDK 的
api_key/apiKey设置为用户在 OneKeyToken 控制台创建的 Key:
- 个人 API Key:形如
okt-user-live-...,用于当前账号自己调用模型。 - 应用接入 Key:形如
okt-app-live-...,用于服务端 B2B2C 应用调用模型。
- 只在服务端保存和使用 Key,不要把 Key 放到浏览器、移动端 App、小程序或公开仓库。
- 调用
POST /chat/completions,完整 URL 是https://my.onekeytoken.com/v1/chat/completions。 - 每次业务调用生成唯一
x-request-id,例如req_order_20260615_0001。同一账号或同一应用下重复使用会返回409 conflict。 - 使用应用接入 Key 时,优先传
x-customer-id标识你的客户;如需统计终端用户,再传x-user-id。传了x-user-id就必须同时传x-customer-id。 model使用控制台开放的模型 code,例如onekeytoken/auto、deepseek-chat、qwen3.8-flash。推荐生产默认使用onekeytoken/auto,由平台按模型策略自动路由。- 交互式场景推荐使用
stream: true,按 OpenAI 兼容 SSE 增量读取;批处理场景可使用stream: false。 - 流式响应从
choices[0].delta.content(推理模型还可能有reasoning_content)读取,非流式响应从choices[0].message.content读取;从调用日志或账单中按x-request-id对账。
可复制的最小配置:
onekeytoken:
protocol: openai_chat_completions_compatible
base_url: https://my.onekeytoken.com/v1
chat_completions_url: https://my.onekeytoken.com/v1/chat/completions
responses_url: https://my.onekeytoken.com/v1/responses
embeddings_url: https://my.onekeytoken.com/v1/embeddings
realtime_asr_url: wss://my.onekeytoken.com/v1/audio/transcriptions/realtime
file_asr_url: https://my.onekeytoken.com/v1/audio/transcriptions
auth:
header: Authorization
value: Bearer ${ONEKEYTOKEN_API_KEY}
required_headers:
Content-Type: application/json
x-request-id: unique id per credential owner
recommended_model: onekeytoken/auto
production_default:
stream: true
attribution:
application_key:
customer_id_header: x-customer-id
user_id_header: x-user-id
rule: x-user-id requires x-customer-id
response_fields:
assistant_text: choices[0].message.content
token_usage: usage
route_info: routing
retry_rule:
do_not_retry_with_same_x_request_id_after_409: true
2. 接入前准备
2.1 创建 Key
在 OneKeyToken 个人中心或应用控制台创建 Key。
| Key 类型 | 前缀 | 使用场景 | 余额扣减 | 是否允许前端暴露 |
|---|---|---|---|---|
| 个人 API Key | okt-user-live-... | 开发者、脚本、后端服务直接调用 | 当前账号钱包 | 不允许 |
| 应用接入 Key | okt-app-live-... | SaaS / B2B2C 应用服务端代理客户请求 | 有 customer_id 时扣客户钱包;无 customer_id 时扣应用所属账号钱包 | 不允许 |
Key 必须具备 model:invoke 权限,否则网关会返回:
{
"error": {
"code": "forbidden",
"message": "Application key cannot invoke models"
}
}
2.2 设置模型范围
控制台中的模型范围会限制 Key 可调用的模型。调用时:
model为空、auto或onekeytoken/auto:走平台自动路由。model是具体模型 code:网关会校验账号、应用和 Key 是否允许该模型。- 如果模型不在允许范围内,返回
403 forbidden。
2.3 充值和余额
调用模型会按上游返回的 usage 计算人民币金额并扣减钱包金额余额,同时写入账本。Token 仅用于记录实际用量和统计,不作为可调用额度门槛。
- 个人 API Key:扣当前账号钱包。
- 应用接入 Key +
x-customer-id:扣该应用下对应客户钱包。 - 应用接入 Key + 不传
x-customer-id:扣应用所属账号钱包,适合内部测试或统一余额模式。
3. API 基础信息
| 项 | 值 |
|---|---|
| 协议 | OpenAI Chat Completions / Responses / Embeddings compatible;Realtime ASR WebSocket;File ASR async JSON |
| Base URL | https://my.onekeytoken.com/v1 |
| Chat Completions | POST https://my.onekeytoken.com/v1/chat/completions |
| Responses | POST https://my.onekeytoken.com/v1/responses |
| Embeddings | POST https://my.onekeytoken.com/v1/embeddings |
| Models | GET https://my.onekeytoken.com/v1/models |
| Realtime ASR | wss://my.onekeytoken.com/v1/audio/transcriptions/realtime |
| File ASR | POST https://my.onekeytoken.com/v1/audio/transcriptions;GET /v1/audio/transcriptions/:id |
| 鉴权方式 | Authorization: Bearer <OneKeyToken Key> |
| 请求格式 | Content-Type: application/json |
| 当前生产建议 | 交互式请求使用 stream: true;批处理使用 stream: false |
当前公开生产接入以 POST /v1/chat/completions 为主;不要使用历史旧地址或非生产域名。
3.1 查询当前 Key 可用模型
调用 GET /v1/models 可以获取当前个人 API Key 或应用接入 Key 实际可调用的 Chat Completions、Responses、Embeddings 与专用 ASR 模型。Key 放在 Authorization Header 中,不要放入 URL、查询参数或前端代码。
curl https://my.onekeytoken.com/v1/models \
-H "Authorization: Bearer $ONEKEYTOKEN_API_KEY"
响应兼容 OpenAI Models API 的 object: "list" 和 data[].id。同时返回平台扩展字段,例如 display_name、capabilities、input_modalities、supported_endpoints、managed_tools、region 和 status:
{
"object": "list",
"data": [
{
"id": "onekeytoken/auto",
"object": "model",
"owned_by": "onekeytoken",
"display_name": "OneKeyToken Auto Router",
"capabilities": ["chat", "auto_routing", "vision"],
"input_modalities": ["text", "image"],
"status": "active"
},
{
"id": "qwen3.8-flash",
"object": "model",
"owned_by": "alibaba-cloud",
"display_name": "Qwen3.8-Flash",
"capabilities": ["chat", "reasoning", "vision", "video_understanding", "function_calling", "structured_output"],
"input_modalities": ["text", "image", "video"],
"output_modalities": ["text"],
"supported_endpoints": ["chat.completions"],
"max_context_tokens": 1000000,
"max_output_tokens": 131072,
"region": "cn",
"status": "active"
}
]
}
- Key 或应用模型范围为空时,表示不额外限制,接口返回全部已启用且网关可调用的 Chat Completions、Embeddings 与专用协议模型。
- 应用 Key 同时受应用模型策略和 Key 自身模型范围约束;两个范围都有配置时取交集。
- 应用模型策略新增模型不会自动扩大已有 Key 的静态白名单;如果模型在应用控制台可见、但外部
GET /v1/models不返回,请编辑对应应用接入 Key 并显式加入该模型。 - 已禁用模型、上游已禁用模型,以及尚未提供对应 API 适配的图片生成、实时等 catalog-only 模型不会返回。
GPT Image等模型仍可在控制台目录中配置,待 Images API 上线后再开放给第三方调用。 onekeytoken/auto是平台虚拟模型。它会在当前 Key 至少有一个可调用聊天模型时出现,调用时由平台自动选取实际模型。- Auto 的
capabilities和input_modalities会根据当前 Key 的候选模型动态生成。候选范围内至少有一个视觉模型时会返回vision和image;纯文本 Key 不会误报图片能力。 - 使用 Auto 发送图片时,平台只会在支持图片输入的候选模型中选择,不会把图片请求降级到纯文本模型;如果当前 Key 没有可用视觉模型,请求会返回没有匹配模型的错误。
supported_endpoints包含responses时才可调用/v1/responses;managed_tools是该模型在平台已完成参数与计费适配的受管工具列表。qwen3.8-flash已正式支持/v1/chat/completions,可接收文本、图片和视频输入;当前不声明 Responses 受管工具,调用方必须以/v1/models返回的supported_endpoints为准。
OpenAI JavaScript SDK 可直接调用:
const client = new OpenAI({
apiKey: process.env.ONEKEYTOKEN_API_KEY,
baseURL: "https://my.onekeytoken.com/v1"
});
const models = await client.models.list();
console.log(models.data.map((model) => model.id));
3.2 文本向量 Embeddings
POST /v1/embeddings 兼容 OpenAI Embeddings API。必须指定具体向量模型,不能使用 onekeytoken/auto。当前开放模型为 qwen3.7-text-embedding,支持单个字符串或最多 20 个字符串组成的数组。
curl https://my.onekeytoken.com/v1/embeddings \
-H "Authorization: Bearer $ONEKEYTOKEN_API_KEY" \
-H "Content-Type: application/json" \
-H "x-request-id: req_embedding_20260902_0001" \
-d '{
"model": "qwen3.7-text-embedding",
"input": ["第一段文本", "second text"],
"dimensions": 1024,
"encoding_format": "float"
}'
OpenAI JavaScript SDK 可直接调用:
const response = await client.embeddings.create({
model: "qwen3.7-text-embedding",
input: ["第一段文本", "second text"],
dimensions: 1024
});
console.log(response.data[0].embedding);
Embedding 请求沿用相同 Key 范围、model:invoke scope、金额余额预检、请求幂等和应用客户归因规则。语音模型使用下述专用接口,不可提交到 Embeddings 或 Chat Completions 路径。
3.3 受管 Responses 工具
Responses 协议已在生产开放;具体模型和工具仍以当前 Key 调用 /v1/models 返回的能力为准。
POST /v1/responses 使用原生 Responses JSON/SSE 契约。首个正式支持模型为 qwen3.7-plus,必须显式指定模型,不能使用 onekeytoken/auto。
| 产品场景 | 推荐 tools[].type | 上游参数 |
|---|---|---|
| 代码解释器 | code_interpreter | [{"type":"code_interpreter"}] |
| 联网搜索 | web_search | [{"type":"web_search"}] |
| 网页抓取 | web_extractor | [ {"type":"web_search"}, {"type":"web_extractor"} ] |
平台也接受产品内部使用的连字符别名 code-interpreter、web-search、web-extractor,并在发往百炼前映射为上表的标准下划线名称。请求网页抓取时,平台会自动补齐并去重其必需的 web_search 工具。第三方集成仍建议直接使用标准名称。
非流式示例:
curl https://my.onekeytoken.com/v1/responses \
-H "Authorization: Bearer $ONEKEYTOKEN_API_KEY" \
-H "Content-Type: application/json" \
-H "x-request-id: req_responses_code_20260904_0001" \
-d '{
"model": "qwen3.7-plus",
"input": "使用代码计算 123 的 21 次方,并只返回结果。",
"tools": [{ "type": "code_interpreter" }],
"enable_thinking": true,
"stream": false
}'
OpenAI JavaScript SDK 示例:
const response = await client.responses.create({
model: "qwen3.7-plus",
input: "搜索 OneKeyToken 的最新公开信息,并给出来源。",
tools: [{ type: "web_search" }],
stream: false
}, {
headers: { "x-request-id": `req_responses_search_${Date.now()}` }
});
console.log(response.output_text);
console.log(response.usage?.x_tools);
流式请求传 stream: true。网关逐字节保留 response.created、response.output_text.delta、response.completed 等 typed SSE 事件,不转换成 Chat Completion chunks;客户端应按 Responses SDK 的事件类型消费。最终 response.completed.response.usage 中的 Token 与 x_tools 调用次数用于一次性结算。
费用由两部分组成:模型输入/输出 Token 费和受管工具调用费。当前华北 2(北京)目录原价中,联网搜索为 4 元/千次;代码解释器与网页抓取处于供应商限时免费阶段但仍显式记录调用次数和零价。平台只在模型目录存在明确价格(包括明确的零价)时开放工具,避免优惠结束后静默漏计费;应用价表可覆盖客户侧工具售价。
3.4 实时语音转写 WebSocket
同一路径支持阿里百炼realtime-asr与讯飞iflytek-realtime-asr。模型只有同时进入应用策略与当前 Key 白名单,并具备正数语音价格、对应语音渠道凭证和已验证运行时,才会由/v1/models返回。
连接地址:
wss://my.onekeytoken.com/v1/audio/transcriptions/realtime?model=realtime-asr&max_audio_seconds=300&recognized_language=zh
- Header 使用
Authorization: Bearer <OneKeyToken Key>;应用归因可继续传x-customer-id和x-user-id,也可使用同名查询参数。 - 音频格式固定为 PCM
pcm_s16le、16kHz、16bit、单声道。二进制消息只放音频数据,不要 Base64 或包 JSON。 - 收音完成后发送文本事件
{"type":"input_audio_buffer.commit"};平台会映射为所选供应商的结束序列。 max_audio_seconds声明本次允许的最大音频时长,默认 60,范围 1 到 28800。平台在连接上游前按该上限预检金额,最终只按实际转发音频的向上取整秒数结算。- 建议每 40ms 发送 1280 字节 PCM;发送录音文件时也要按实时速度节流,避免上游以发送过快拒绝请求。
realtime-asr支持recognized_language,不提供发言人区分,完成事件中speaker_diarization=false、分段 speaker 为null。iflytek-realtime-asr可用role_type=2开启发言人区分,role_type=0关闭。- 两个实时模型都不会进入
onekeytoken/auto;能否发现和调用以当前 Key 的/v1/models为准。
Node.js 使用 ws 的最小示例:
import { createReadStream } from "node:fs";
import { setTimeout as delay } from "node:timers/promises";
import WebSocket from "ws";
const url = new URL("wss://my.onekeytoken.com/v1/audio/transcriptions/realtime");
url.searchParams.set("model", "realtime-asr");
url.searchParams.set("max_audio_seconds", "300");
url.searchParams.set("recognized_language", "zh");
const socket = new WebSocket(url, {
headers: {
Authorization: `Bearer ${process.env.ONEKEYTOKEN_API_KEY}`,
"x-request-id": `req_asr_${Date.now()}`
}
});
socket.on("message", async (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "transcription.session.started") {
const audio = createReadStream("meeting-16k-mono.pcm", { highWaterMark: 1280 });
for await (const frame of audio) {
socket.send(frame);
await delay(40);
}
socket.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
}
if (event.type === "transcription.delta") process.stdout.write(event.text);
if (event.type === "transcription.completed") console.log("\n", event);
if (event.type === "error") console.error(event.error);
});
服务端事件:
type | 说明 |
|---|---|
transcription.session.started | 上游 WSS 已建立,可以开始发送音频帧。 |
transcription.delta | 临时识别结果,后续可能被同一 segment_id 的最终结果替换。 |
transcription.segment | 已确认的识别段;仅支持发言人区分的模型会填写 segments[].speaker。 |
transcription.completed | 完整文本、最终分段、实际时长、计费秒数和扣费金额。 |
error | 协议、上游或结算错误;失败会记失败流水但不扣费。 |
完成事件示例:
{
"type": "transcription.completed",
"session_id": "session-id",
"text": "今天讨论第一项。",
"segments": [
{ "id": "1", "speaker": "speaker_1", "text": "今天讨论第一项。", "final": true }
],
"usage": {
"audio_bytes": 963840,
"audio_duration_seconds": 30.12,
"billable_audio_seconds": 31
},
"billing": {
"currency": "CNY",
"charge_amount_cents": 4,
"price_version": "model_catalog"
}
}
3.5 文件语音转写异步任务
file-asr 使用 JSON 异步任务,不是 OpenAI multipart 上传。音频必须先放在阿里云上游可访问的公网 HTTPS 地址;单文件最长 12 小时、最大 2GB。提交时必须声明可信的时长上限,平台先按上限做金额预检,成功后以供应商返回的实际音频秒数结算。
提交任务:
curl https://my.onekeytoken.com/v1/audio/transcriptions \
-H "Authorization: Bearer $ONEKEYTOKEN_API_KEY" \
-H "Content-Type: application/json" \
-H "x-request-id: req_file_asr_20260904_0001" \
-d '{
"model": "file-asr",
"file_url": "https://your-public-bucket.example.com/meeting.wav",
"max_audio_seconds": 3600,
"language": "zh",
"enable_itn": true,
"enable_words": true,
"channel_id": [0]
}'
返回 HTTP 202:
{
"id": "asrjob_...",
"object": "audio.transcription.job",
"status": "pending",
"model": "file-asr",
"created_at": 1788516000
}
使用同一应用下的任一有效 Key(个人任务则必须属于同一账号)查询:
curl https://my.onekeytoken.com/v1/audio/transcriptions/asrjob_... \
-H "Authorization: Bearer $ONEKEYTOKEN_API_KEY"
status 为 pending、running、succeeded 或 failed。成功时返回 result.audio_info、result.transcripts[].text/sentences/words 和 billing.billable_audio_seconds;平台不会返回上游 task id、临时结果下载 URL 或原始 file_url。供应商失败、超时或无有效 usage 时写失败调用记录但不扣费。客户端建议按 3 秒或更慢的间隔轮询,避免无意义高频查询。
4. Header 说明
| Header | 必填 | 示例 | 说明 |
|---|---|---|---|
Authorization | 是 | Bearer okt-app-live-... | OneKeyToken Key。必须是 Bearer 方案。 |
Content-Type | 是 | application/json | 请求体必须是 JSON。 |
x-request-id | 强烈建议 | req_20260615_0001 | 调用幂等与对账 ID。同一账号或同一应用下必须唯一。未传时网关会生成,但调用方不容易对账。 |
x-customer-id | 应用 Key 推荐 | tenant_001 | 你的业务客户 ID。用于客户钱包、客户账单、客户专有上游 Key 规则。 |
x-user-id | 可选 | user_9281 | 客户下终端用户 ID。传它时必须同时传 x-customer-id。 |
x-oktoken-customer-id | 可选 | tenant_001 | x-customer-id 的等价别名。 |
x-oktoken-user-id | 可选 | user_9281 | x-user-id 的等价别名。 |
x-oktoken-routing-debug | 调试可选 | 1 | 返回更详细的自动路由诊断。生产普通请求不建议常开。 |
x-oktoken-perf-timings | 调试可选 | 1 | 返回 Server-Timing 与网关内部耗时 header。排障时使用。 |
5. 请求体参数
OneKeyToken 会把 OpenAI 兼容参数转发给上游模型,但会先读取部分扩展字段做鉴权、路由、归因和扣费。
5.1 标准 Chat Completions 参数
| 参数 | 类型 | 必填 | 示例 | 说明 |
|---|---|---|---|---|
model | string | 推荐 | onekeytoken/auto | 平台模型 code。为空、auto 或 onekeytoken/auto 时走自动路由。 |
messages | array | 是 | [{"role":"user","content":"你好"}] | 对话消息数组,不能为空。 |
messages[].role | string | 是 | system / user / assistant / tool | 消息角色。具体支持范围取决于上游模型。 |
messages[].content | string 或数组 | 是 | "生成摘要" | 消息内容。文本模型通常传字符串;图片输入使用 OpenAI 兼容的 image_url 内容块。Auto 会保留并转发完整图片内容。 |
temperature | number | 否 | 0.2 | 随机性,常用范围 0 到 2。 |
top_p | number | 否 | 0.9 | nucleus sampling 参数。 |
max_tokens | integer | 否 | 512 | 最大输出 Token。部分新模型也支持 max_completion_tokens。 |
stream | boolean | 否 | false | true 返回 OpenAI 兼容 SSE,并以 data: [DONE] 结束;false 返回完整 JSON。交互式场景推荐 true。 |
stop | string 或 array | 否 | ["\n\n"] | 停止生成标记。 |
presence_penalty | number | 否 | 0 | 话题新颖性惩罚,是否生效取决于上游。 |
frequency_penalty | number | 否 | 0 | 重复惩罚,是否生效取决于上游。 |
response_format | object | 否 | {"type":"json_object"} | JSON 输出等结构化要求,模型支持时生效。 |
tools | array | 否 | OpenAI tools schema | 工具调用定义,模型支持时生效。 |
tool_choice | string 或 object | 否 | auto | 工具选择策略。 |
5.2 OneKeyToken 扩展参数
这些字段也可放在请求体中,但生产建议优先使用 Header,便于网关、代理和日志统一处理。
| 参数 | 类型 | 必填 | 示例 | 说明 |
|---|---|---|---|---|
customer_id | string | 应用 Key 推荐 | tenant_001 | 与 x-customer-id 等价。平台不会转发给上游。 |
customerId | string | 应用 Key 推荐 | tenant_001 | customer_id 的 camelCase 形式。 |
user_id | string | 可选 | user_9281 | 与 x-user-id 等价。必须配合 customer_id。平台不会转发给上游。 |
userId | string | 可选 | user_9281 | user_id 的 camelCase 形式。 |
user | string | 可选 | user_9281 | user_id 的兼容别名。 |
不要把 request_id 放在请求体里作为幂等依据。公开网关读取的是 x-request-id Header。
6. 调用示例
6.1 curl:个人 API Key(流式)
curl -N https://my.onekeytoken.com/v1/chat/completions \
-H "Authorization: Bearer okt-user-live-REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "x-request-id: req_personal_20260615_0001" \
-d '{
"model": "onekeytoken/auto",
"messages": [
{ "role": "system", "content": "你是一个简洁可靠的中文助手。" },
{ "role": "user", "content": "用三句话介绍 OneKeyToken 的网关接入方式。" }
],
"temperature": 0.2,
"max_tokens": 512,
"stream": true
}'
6.2 curl:应用接入 Key + 客户归因
curl https://my.onekeytoken.com/v1/chat/completions \
-H "Authorization: Bearer okt-app-live-REPLACE_WITH_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "x-request-id: req_app_20260615_0001" \
-H "x-customer-id: tenant_bank_001" \
-H "x-user-id: employee_2381" \
-d '{
"model": "onekeytoken/auto",
"messages": [
{ "role": "user", "content": "总结这张工单的处理建议:客户反馈发票抬头错误,需要重开。" }
],
"temperature": 0.2,
"max_tokens": 512,
"stream": false
}'
6.3 JavaScript:OpenAI SDK
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.ONEKEYTOKEN_API_KEY,
baseURL: "https://my.onekeytoken.com/v1"
});
const requestId = `req_js_${Date.now()}`;
const stream = await client.chat.completions.create(
{
model: "onekeytoken/auto",
messages: [
{ role: "system", content: "你是企业知识库助手,回答要简洁。" },
{ role: "user", content: "请把下面的客户反馈整理成三条待办。" }
],
temperature: 0.2,
max_tokens: 512,
stream: true
},
{
headers: {
"x-request-id": requestId,
"x-customer-id": "tenant_bank_001",
"x-user-id": "employee_2381"
}
}
);
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
process.stdout.write(delta?.reasoning_content || delta?.content || "");
}
process.stdout.write("\n");
6.4 JavaScript:fetch
const response = await fetch("https://my.onekeytoken.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ONEKEYTOKEN_API_KEY}`,
"Content-Type": "application/json",
"x-request-id": `req_fetch_${Date.now()}`,
"x-customer-id": "tenant_bank_001"
},
body: JSON.stringify({
model: "deepseek-chat",
messages: [{ role: "user", content: "生成一份 100 字以内的日报摘要。" }],
temperature: 0.2,
max_tokens: 256,
stream: true
})
});
if (!response.ok) {
const body = await response.json();
throw new Error(`${body.error?.code}: ${body.error?.message}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split("\n\n");
buffer = events.pop() || "";
for (const event of events) {
const data = event.split("\n")
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trimStart())
.join("\n");
if (!data || data === "[DONE]") continue;
const chunk = JSON.parse(data);
if (chunk.error) throw new Error(`${chunk.error.code}: ${chunk.error.message}`);
const delta = chunk.choices?.[0]?.delta;
process.stdout.write(delta?.reasoning_content || delta?.content || "");
}
}
6.5 Python:OpenAI SDK
import os
import time
from openai import OpenAI
client = OpenAI(
api_key=os.environ["ONEKEYTOKEN_API_KEY"],
base_url="https://my.onekeytoken.com/v1",
)
stream = client.chat.completions.create(
model="onekeytoken/auto",
messages=[
{"role": "system", "content": "你是客服质检助手。"},
{"role": "user", "content": "请判断这段客服回复是否礼貌,并给出改写建议。"},
],
temperature=0.2,
max_tokens=512,
stream=True,
extra_headers={
"x-request-id": f"req_py_{int(time.time() * 1000)}",
"x-customer-id": "tenant_bank_001",
"x-user-id": "agent_2381",
},
)
for chunk in stream:
delta = chunk.choices[0].delta if chunk.choices else None
if delta:
print(getattr(delta, "reasoning_content", None) or delta.content or "", end="", flush=True)
print()
6.6 Python:requests
import os
import time
import requests
resp = requests.post(
"https://my.onekeytoken.com/v1/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['ONEKEYTOKEN_API_KEY']}",
"Content-Type": "application/json",
"x-request-id": f"req_requests_{int(time.time() * 1000)}",
"x-customer-id": "tenant_bank_001",
},
json={
"model": "qwen3.5-flash",
"messages": [{"role": "user", "content": "请给我一个会议纪要模板。"}],
"temperature": 0.2,
"max_tokens": 512,
"stream": False,
},
timeout=70,
)
data = resp.json()
if resp.status_code >= 400:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data["choices"][0]["message"]["content"])
6.7 Node.js 后端代理:不要把 Key 暴露给前端
import express from "express";
import OpenAI from "openai";
const app = express();
app.use(express.json());
const client = new OpenAI({
apiKey: process.env.ONEKEYTOKEN_APP_KEY,
baseURL: "https://my.onekeytoken.com/v1"
});
app.post("/api/ai/chat", async (req, res, next) => {
try {
const currentTenantId = req.user.tenantId;
const currentUserId = req.user.id;
const requestId = `req_${currentTenantId}_${Date.now()}`;
const completion = await client.chat.completions.create(
{
model: "onekeytoken/auto",
messages: [
{ role: "system", content: "你是当前 SaaS 产品内置的 AI 助手。" },
{ role: "user", content: req.body.question }
],
temperature: 0.2,
max_tokens: 512,
stream: false
},
{
headers: {
"x-request-id": requestId,
"x-customer-id": currentTenantId,
"x-user-id": currentUserId
}
}
);
res.json({
requestId,
answer: completion.choices[0]?.message?.content || "",
usage: completion.usage,
routing: completion.routing
});
} catch (error) {
next(error);
}
});
app.listen(3000);
7. 响应说明
非流式成功响应兼容 OpenAI Chat Completions,并额外带 routing 字段。
{
"id": "chatcmpl_...",
"object": "chat.completion",
"created": 1781510000,
"model": "qwen3.5-flash",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "这里是模型回答。"
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 128,
"completion_tokens": 256,
"total_tokens": 384
},
"routing": {
"model_used": "qwen3.5-flash",
"policy": "smart_balanced",
"request_type": "general",
"reason": "auto route selected by policy"
}
}
| 字段 | 说明 |
|---|---|
choices[0].message.content | AI 回答文本。 |
usage.prompt_tokens | 输入 Token 数。 |
usage.completion_tokens | 输出 Token 数。 |
usage.total_tokens | 总 Token 数,账单和用量统计会优先使用它。 |
routing.model_used | 实际路由到的平台模型 code。 |
routing.policy | 本次路由策略,例如 smart_balanced。 |
routing.request_type | 平台识别的请求类型。 |
routing.reason | 路由原因摘要。 |
当请求头带 x-oktoken-routing-debug: 1 时,routing 会包含候选模型、评分、权重等更多调试字段。该模式适合排障,不建议在普通生产请求中常开。
7.1 流式响应
stream: true 返回 Content-Type: text/event-stream。每个事件的 data 是一个 OpenAI 兼容 chat.completion.chunk,结束标记为 data: [DONE]:
data: {"id":"chatcmpl_...","object":"chat.completion.chunk","model":"deepseek-chat","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl_...","object":"chat.completion.chunk","model":"deepseek-chat","choices":[{"index":0,"delta":{"content":"你好"},"finish_reason":null}]}
data: [DONE]
- 文本增量位于
choices[0].delta.content。 - 推理模型可同时返回
choices[0].delta.reasoning_content。 - 工具调用增量位于
choices[0].delta.tool_calls。 - 上游提供用量时,结束前的 chunk 可能包含
usage;即使上游不返回流式usage,平台也会完成用量估算、结算和账本记录。 - 首个 chunk 之前失败时,网关保留对应 HTTP 状态并返回标准 JSON 错误;已经开始输出后失败时,会发送
event: error的 SSE 事件,然后发送[DONE]。
8. 计费、账本和对账
8.1 扣费规则
OneKeyToken 使用上游返回的 usage 计算扣费:
扣费金额 = ceil(input_tokens * 输入单价 / 1,000,000 + output_tokens * 输出单价 / 1,000,000)
扣费 Token = usage.total_tokens 或 input_tokens + output_tokens
说明:
- 价格单位是“分 / 百万 Token”存储,前端通常展示为“元 / M Token”。
- 应用价表存在时,应用 Key 调用优先使用应用价表销售价。
- 无应用价表覆盖时,使用模型目录价。
- 模型成本仍按模型目录成本价记录,用于平台毛利和上游成本统计。
8.2 归因规则
| 调用方式 | 归因字段 | 账本 |
|---|---|---|
| 个人 API Key | 无需 customer_id | 账号钱包、账号账本 |
应用 Key,不传 customer_id | 应用、应用 Key | 应用所属账号钱包、账号账本 |
应用 Key,传 customer_id | 应用、客户 | 客户钱包、客户账本 |
应用 Key,传 customer_id + user_id | 应用、客户、终端用户 | 客户钱包、客户账本,附带终端用户归因 |
x-customer-id 是你的业务客户 ID,不需要提前在 OneKeyToken 手工创建。第一次调用时平台会按应用自动建立客户与客户钱包;如果余额不足,需要先在应用客户余额里充值或发放额度。
8.3 request_id 幂等与对账
x-request-id 是排查和对账的关键字段。
- 每次新的模型调用必须使用新的
x-request-id。 - 同一账号或同一应用下重复使用会返回
409 conflict。 - 不同应用可以使用相同字符串,但不建议这样做,跨系统排查容易混淆。
- 建议格式包含业务系统、场景、时间和随机串,例如
req_crm_ticket_20260615_7f3a9c。
如果客户端网络超时但服务端可能已经完成扣费,不要盲目用新的 x-request-id 重试同一业务动作;应先用你的业务日志、OneKeyToken 调用日志或账单按旧 x-request-id 查询是否已完成。
9. 错误格式与排查
错误响应统一为:
{
"error": {
"code": "bad_request",
"message": "messages must be a non-empty array",
"details": {}
}
}
常见错误:
| HTTP | code | 常见 message | 处理方式 |
|---|---|---|---|
| 400 | bad_request | Request body must be valid JSON | 检查 JSON 格式和 Content-Type。 |
| 400 | bad_request | messages must be a non-empty array | messages 必须是非空数组。 |
| 400 | bad_request | x-customer-id is required when x-user-id is provided | 传 x-user-id 时补上 x-customer-id。 |
| 401 | unauthorized | Application key is required | 检查 Authorization: Bearer ... 是否存在。 |
| 401 | unauthorized | Invalid API key | Key 错误、已轮换、复制不完整或不是 OneKeyToken Key。 |
| 403 | forbidden | Application key cannot invoke models | 给应用 Key 增加 model:invoke scope,或换可调用模型的 Key。 |
| 403 | forbidden | Model is not allowed by this application key | 检查应用模型策略和 Key 的模型范围。 |
| 402 | insufficient_balance | Customer monetary balance is insufficient | 给对应客户钱包充值金额余额后再试;请求会在调用上游前被拦截。 |
| 402 | insufficient_balance | Account monetary balance is insufficient | 给账号钱包充值金额余额后再试;请求会在调用上游前被拦截。 |
| 409 | conflict | request_id has already been used for this credential owner | 生成新的 x-request-id;不要复用旧 ID 发起新调用。 |
| 429 | too_many_requests | RPM guardrail exceeded | 降低并发或在控制台调整限流。 |
| 502 | bad_gateway | Upstream provider request failed | 上游供应商、上游 Key 池或模型配置异常,稍后重试或联系运营。 |
| 504 | gateway_timeout | Upstream provider timed out | 上游超时,缩短输出、切换模型或稍后重试。 |
WebSocket 握手失败时沿用上表 HTTP 状态与 JSON 错误体。升级成功后的错误使用 {"type":"error","error":{"code":"...","message":"..."}},随后连接关闭。max_audio_seconds_exceeded、audio_idle_timeout、invalid_audio_frame 和 upstream_closed_early 不会扣费;使用新的 x-request-id 重连。
10. 模型选择建议
10.1 推荐模型写法
生产推荐:
{
"model": "onekeytoken/auto"
}
平台会根据应用/账号模型范围、请求特征、价格、质量、延迟和健康状态选择实际模型。
如果业务必须固定模型,可以传具体模型 code:
{
"model": "deepseek-chat"
}
或:
{
"model": "qwen3.5-flash"
}
最终可用模型以控制台“模型调度”和“Key 模型范围”为准。文档示例只表示接入方式,不保证每个账号都已开通。
10.2 客户专有供应商 Key
运营可以在应用详情中配置“客户专有供应商模型 Key”。调用方不需要传供应商 Key,只需要正常传:
- 应用接入 Key
x-customer-idmodel
如果该应用、客户、模型存在专有上游 Key 绑定,网关会优先使用该专有 Key 转发上游;否则使用模型所属供应商的共享 Key 池策略。
11. 安全最佳实践
- Key 只放服务端环境变量、密钥管理系统或后端配置中心。
- 一个项目、一个环境、一个用途创建独立 Key,便于限流、禁用和审计。
- 生产 Key 建议配置 IP 白名单、RPM/TPM 限制和模型范围。
- 日志中只记录 Key 前缀,不记录完整 Key。
- 客户端请求你的后端时,用你自己的登录态鉴权;由你的后端再调用 OneKeyToken。
- 离职、泄露、上线切换后及时轮换 Key。
12. 生产接入检查清单
上线前请逐项确认:
- [ ] 已创建生产个人 API Key 或应用接入 Key。
- [ ] Key 状态为启用,scope 包含
model:invoke。 - [ ] Key 的模型范围包含要调用的模型,或允许
onekeytoken/auto自动路由。 - [ ] 钱包有足够余额;应用客户模式下,目标
customer_id对应客户已有余额。 - [ ] 服务端已设置
ONEKEYTOKEN_API_KEY或等价安全密钥配置。 - [ ] 请求带唯一
x-request-id。 - [ ] 应用 Key 的生产请求带
x-customer-id;如需终端用户统计,也带x-user-id。 - [ ] 业务代码处理
401、402、403、409、429、502、504。 - [ ] 业务日志记录
x-request-id、customer_id、user_id、模型 code、耗时和错误码。 - [ ] 完成 1 VU 小流量 canary,并核对调用日志、账本、余额扣减和用量统计闭合。
13. AI 自动接入规范
下面这段 JSON 可以直接给自动化接入 Agent 使用:
{
"service": "OneKeyToken",
"protocol": "openai_chat_completions_compatible",
"base_url": "https://my.onekeytoken.com/v1",
"endpoints": {
"chat_completions": {
"method": "POST",
"path": "/chat/completions",
"url": "https://my.onekeytoken.com/v1/chat/completions",
"streaming_default": true
}
},
"authentication": {
"type": "bearer",
"header": "Authorization",
"value_template": "Bearer ${ONEKEYTOKEN_API_KEY}",
"key_prefixes": {
"personal": "okt-user-live-",
"application": "okt-app-live-"
},
"server_side_only": true
},
"required_request_body": {
"messages": [
{
"role": "user",
"content": "string"
}
]
},
"recommended_request_body": {
"model": "onekeytoken/auto",
"temperature": 0.2,
"max_tokens": 512,
"stream": true
},
"required_headers": {
"Content-Type": "application/json",
"x-request-id": "unique per account/application credential owner"
},
"application_key_headers": {
"x-customer-id": "business customer id, recommended",
"x-user-id": "end user id, optional, requires x-customer-id"
},
"do_not_send": {
"request_id_in_body": "not used for gateway idempotency",
"api_key_to_browser": "never expose credentials client-side"
},
"success_response_paths": {
"assistant_text": "choices[0].message.content",
"usage": "usage",
"actual_model": "routing.model_used",
"routing_policy": "routing.policy"
},
"error_response_shape": {
"error": {
"code": "string",
"message": "string",
"details": "object optional"
}
},
"retry_policy": {
"409_conflict": "do not retry with same x-request-id for a new call",
"402_insufficient_balance": "recharge the billed wallet before retrying",
"429_too_many_requests": "back off and reduce concurrency",
"502_504": "retry with exponential backoff if business action is still needed; preserve idempotency strategy"
}
}
14. 常见问题
Q1:可以直接把 OneKeyToken Key 放到前端调用吗?
不可以。无论个人 API Key 还是应用接入 Key,都必须放在服务端。前端应调用你自己的后端接口,由后端转发到 OneKeyToken。
Q2:为什么重复请求提示 request_id has already been used?
x-request-id 是同一账号或同一应用下的唯一调用 ID。你把已经用过的 ID 用在了新的请求上。新请求生成新的 ID;网络异常排查时先按旧 ID 查日志,确认是否已经扣费。
Q3:应用 Key 不传 customer_id 可以吗?
可以,但会扣应用所属账号钱包,不会形成客户钱包归因。正式 B2B2C 业务建议始终传 x-customer-id。
Q4:为什么传了 user_id 报错?
user_id 必须挂在某个客户下面。传 x-user-id 时同时传 x-customer-id。
Q5:为什么响应里没有我请求的模型?
如果传的是 onekeytoken/auto、auto 或不传 model,平台会自动路由。看响应里的 routing.model_used 才是实际模型。
Q6:怎么排查慢请求?
临时加请求头:
x-oktoken-perf-timings: 1
x-oktoken-routing-debug: 1
响应会带网关耗时和详细路由信息。排障完成后关闭这些调试 header。
Q7:当前支持流式输出吗?
支持。POST /v1/chat/completions 传 stream: true 即可使用 OpenAI 兼容 SSE;网关会逐 chunk 刷新,并以 data: [DONE] 结束。服务端 SDK 可直接使用 OpenAI SDK 的流式迭代接口。批处理任务仍可使用 stream: false。
Q8:如何按 Key 查询可用模型?
用这个 Key 调用 GET /v1/models 即可。接口只返回该 Key 当前可调用且运行配置完整的模型,并通过 capabilities、input_modalities 和模型字段说明所需协议;如果返回 401 unauthorized,检查 Key 是否正确或已轮换;如果返回 403 forbidden,检查 Key、所属账号或应用状态,并确认 scope 包含 model:invoke。
Q9:Embedding 可以使用 Auto Router 吗?
不可以。POST /v1/embeddings 必须指定具体 Embedding 模型,例如 qwen3.7-text-embedding。onekeytoken/auto 只用于 Chat Completions。
Q10:语音模型可以使用 Auto Router 或 OpenAI SDK 吗?
不可以使用 Auto Router,也不能传给 Chat Completions。realtime-asr 与 iflytek-realtime-asr 使用独立 WebSocket 二进制协议,可用任意标准 WebSocket 客户端;file-asr 使用平台的 JSON 提交/查询接口。通用 OpenAI SDK 未必封装这些扩展协议,建议直接使用 WebSocket 或 HTTP 客户端。
15. 完整非流式请求模板
{
"model": "onekeytoken/auto",
"messages": [
{
"role": "system",
"content": "你是一个可靠、简洁、可审计的企业 AI 助手。"
},
{
"role": "user",
"content": "请总结以下客户反馈,并输出三条处理建议。"
}
],
"temperature": 0.2,
"max_tokens": 512,
"stream": false
}
对应 Header:
Authorization: Bearer okt-app-live-REPLACE_WITH_YOUR_KEY
Content-Type: application/json
x-request-id: req_your_system_20260615_0001
x-customer-id: tenant_001
x-user-id: user_001