客户端 HTTP API
本规范描述客户端与 ScootGate API listener 的当前 HTTP 契约。
通用要求
- 传输:HTTP/1.1;生产环境使用 TLS 1.2+;
- 每个 API listener 请求都必须鉴权,包括
/v1/models; - POST 请求必须带唯一、有效的
Content-Length; - JSON 请求体大小受
api.max_request_body_bytes限制; - 每个响应使用
Connection: close; - 未声明的路径返回 404,不存在管理 API。
鉴权
支持两种请求头:
Authorization: Bearer <scootgate-client-key>
x-api-key: <scootgate-client-key>
两者同时出现时,有效 Bearer 值优先。key 缺失、未知或已吊销时:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Content-Type: application/json
{"status":"unauthorized"}
客户端 key 只用于 ScootGate 鉴权,不会转发给 Provider。
支持的端点
| 方法 | 路径 | 协议 |
|---|---|---|
POST | /v1/chat/completions | OpenAI-compatible |
POST | /v1/responses | 可选 Responses-to-Chat 显式适配器 |
POST | /v1/messages | Anthropic Messages |
GET | /v1/models | OpenAI-compatible 模型目录 |
路径存在但方法错误时返回:
{"status":"method_not_allowed"}
状态码为 405。
GET /v1/models
只列出已启用的 OpenAI-compatible 公共模型。Anthropic 模型、backend ID、 真实模型名和 Provider origin 不会出现。
响应示例:
{
"object": "list",
"data": [
{
"id": "chat-fast",
"object": "model",
"owned_by": "scootgate"
}
]
}
POST /v1/chat/completions
请求体必须是 JSON object,并包含字符串 model:
POST /v1/chat/completions HTTP/1.1
Authorization: Bearer demo-client-key
Content-Type: application/json
Content-Length: 67
{"model":"chat-fast","messages":[{"role":"user","content":"ping"}]}
ScootGate 选择 openai_compatible target,把公共 model 改写为真实模型名,
保留其他 JSON 字段,然后注入 backend credential。
当 stream: true 时要求 target 支持 stream,并透明转发 Provider SSE。
POST /v1/responses
该端点默认关闭。只有配置以下开关后才存在:
[api]
responses_to_chat_completions = true
关闭时返回 404。开启后,ScootGate 选择 openai_compatible target,把请求显式
转换后发送至上游 POST /v1/chat/completions;它不是任意协议转换器。
支持的请求子集
| Responses 字段/item | Chat Completions 映射 |
|---|---|
model | 路由公共模型名,再改写为 target 真实模型名 |
字符串 input | 单条 user message |
input message + input_text/output_text | 对应 Chat message + text content |
instructions | 前置 system message |
max_output_tokens | max_tokens |
temperature、top_p | 同名字段 |
stream | 同名字段;流式时强制请求 Provider 返回 usage |
function tools | Chat tools[].function |
tool_choice | auto/none/required 或指定 function |
parallel_tool_calls | 同名字段 |
function_call/function_call_output input item | assistant tool call/tool result message |
store: false、truncation: "disabled" | 无状态适配器的固定语义 |
metadata | 保留在 Responses 响应 envelope,不发送给 Provider |
默认 text.format.type = "text" | 文本输出 |
stream_options.include_obfuscation = false | 接受;适配器不生成 obfuscation |
所有映射或保留字段都在连接 egress 前校验类型:stream 和
parallel_tool_calls 必须是布尔值,metadata 必须是 object,
max_output_tokens 必须是正整数,temperature/top_p 必须是数字,
stream_options 必须是只包含受支持字段的 object。
以下客户端常发字段静默忽略(无 Chat 等价物,不改变路由语义,避免 OpenAI JS/agent 运行时无谓 400):
reasoning(含effort等子字段)include、service_tier、user、prompt_cache_key、safety_identifiermax_tool_calls、top_logprobs
非流式 Chat text、refusal 和 tool_calls 分别转换为 Responses message
中的 output_text、refusal content part 和 function_call output item;
Provider 原生 prompt_tokens/completion_tokens 映射为 Responses usage,
公共模型名不会被上游真实名替换。
明确拒绝的输入
以下内容无法在无状态 Chat Completions 请求中保持语义,因此返回 400,且不会 连接 egress 或 Provider:
previous_response_id、conversation、background、prompt template;- hosted/MCP/computer/file search 等非 function tool;
- image、file、refusal 等未列出的 input content;
store: true、自动 truncation、structured text format、stream obfuscation;- 已支持字段的错误类型或取值;
- context management、moderation 等未列入「支持」或「忽略」的非空字段;
- 任意未知非空字段。
错误采用 OpenAI shape,error.code 为 unsupported_parameter,
error.param 指向拒绝字段或 item。
SSE 转换
上游 Chat SSE(包括 HTTP chunked transfer)会增量转换为 Responses 事件。 文本流包含:
response.created
response.in_progress
response.output_item.added
response.content_part.added
response.output_text.delta
response.output_text.done
response.content_part.done
response.output_item.done
response.completed
refusal 流使用 response.refusal.delta/done;函数调用流使用
response.function_call_arguments.delta/done,两者都不伪造文本 content
事件。
SSE decoder 接受 CR、LF、CRLF 行结束及任意网络 chunk 切分,单条上游 SSE
record 上限为 256 KiB。首个事件必须是合法 Chat SSE,否则在提交流式 200 前
返回 502;之后的 response ID,以及每个 tool index 对应的 call ID/函数名,
必须保持一致。只有 [DONE],或无残留 record 且已出现 finish_reason 的 clean
EOF,才能生成 response.completed;截断、超限、身份变化或 [DONE] 后数据会
终止流且不生成完成事件。客户端断开会关闭 Provider tunnel;ScootGate 不等待
上游生成完毕。
POST /v1/messages
请求体同样必须包含字符串 model:
POST /v1/messages HTTP/1.1
x-api-key: demo-client-key
anthropic-version: 2023-06-01
Content-Type: application/json
Content-Length: 85
{"model":"claude-fast","max_tokens":32,"messages":[{"role":"user","content":"ping"}]}
只选择 anthropic_messages target。anthropic-version 与 anthropic-beta
会转发给 Provider;客户端 x-api-key 会被 backend credential 替换。
能力过滤
ScootGate 从请求体推导所需能力:
| 请求字段 | 所需能力 |
|---|---|
"stream": true | supports_stream |
非空 tools(或非 array 值) | supports_tools |
response_format.type == "json_schema" | supports_json_schema |
模型存在但没有 target 同时满足能力时返回 400,不会出网。
请求头转发
客户端请求头采用 allowlist。只可能转发:
Content-TypeAcceptanthropic-versionanthropic-beta
ScootGate 重建 request line、Host、Provider 鉴权、Content-Length 与
Connection。客户端 Authorization、x-api-key、Host、代理头和其他
自定义头不会转发。
Provider 响应
除以下特殊处理外,ScootGate 转发 Provider 状态码、响应头和 body:
- 移除
Connection、Keep-Alive、Proxy-Connection,Proxy-Authenticate与TE; - 添加
Connection: close; - Provider 401/403 不原样返回:backend 被停用,客户端收到 502;
- Provider 429 原样返回,并让 backend 进入短暂冷却;
- SSE 按字节增量转发;客户端断开会关闭 Provider tunnel;
- Provider body 超过
stream_idle_timeout_ms无数据时结束 relay。
网关错误
通用错误 body
| 状态 | body | 场景 |
|---|---|---|
| 400 | {"status":"bad_request"} | HTTP 头或 framing 不合法 |
| 401 | {"status":"unauthorized"} | key 缺失/未知/吊销 |
| 404 | {"status":"not_found"} | 未知路径 |
| 405 | {"status":"method_not_allowed"} | 方法错误 |
| 411 | {"status":"length_required"} | POST 缺少 Content-Length |
| 413 | {"status":"payload_too_large"} | 请求体超过上限 |
| 502 | {"status":"bad_gateway"} | 出站、TLS、写入、响应头或 Provider 鉴权失败 |
| 503 | {"status":"not_ready"} | snapshot 路由尚未加载 |
| 504 | {"status":"gateway_timeout"} | 响应头预算耗尽 |
OpenAI-shaped 模型错误
无效 JSON、缺少 model、未知模型、能力不支持或无 backend 时使用:
{
"error": {
"message": "model `missing` does not exist",
"type": "not_found_error",
"param": "model",
"code": null
}
}
未知模型为 404;无效请求/能力不足为 400;无可用 backend 为 503。
Anthropic-shaped 模型错误
{
"type": "error",
"error": {
"type": "not_found_error",
"message": "model `missing` does not exist"
}
}
重试语义
ScootGate 不自动重试可能已到达 Provider 的请求。只有在能证明尚未发送
Provider 请求字节时,才可能在 max_pre_send_attempts 范围内改选其他
backend。客户端和外层代理也不应对这些 POST 请求进行无条件自动重试。