22 KiB
Codex Proxy RS 接口
本文列出 v3 当前公开 HTTP 接口。路由事实以
backend/crates/gateway-api/src 中的 router 为准。
1. 鉴权与公共约定
客户端接口
所有 /v1/* 请求都使用管理端创建的 Client Key:
Authorization: Bearer sk_...
Client Key 通过账号分组限定路由范围:未绑定分组时可使用全部账号,绑定一个或多个分组时只能使用
已启用分组成员的并集。分组可以混合 openai 与 xai 账号;同一请求只会在模型能力明确匹配且满足
重放安全边界时跨 Provider fallback。
管理接口
除登录、会话状态和登出外,所有 /api/admin/* 请求都需要以下任一鉴权方式:
- 浏览器登录后得到的
cpr_admin_sessionCookie; x-api-key: <admin-api-key>。
请求无需自带 x-request-id;缺失时服务端自动生成 UUID 并在响应头回传同一 request ID。
api.request_id_header 可改变注入与回传的 header 名,管理端鉴权不依赖该名字。
管理端响应统一带 Cache-Control: no-store。
配置了 CORS 白名单 origin 时,跨域请求以凭据模式放行,仅允许 GET/POST 方法和
authorization、content-type、x-api-key 与 request ID 四个请求头,不使用通配符。
普通成功响应使用以下信封:
{
"code": 200,
"message": "OK",
"data": {}
}
错误响应仍使用 code、message、data,其中 data 为 null。稳定业务码包括参数错误
40001、会话缺失 40101、登录凭据错误 40102、管理 API Key 错误 40103、资源不存在
40401、业务冲突 40901、内部错误 50001、上游失败 50201 和依赖不可用 50301。
管理写入一致性
Admin API 不暴露全局配置版本,mutation 请求也不要求客户端提供全局配置版本。会改变路由快照或安全配置的写入由后端在事务内推进内部配置 revision,并用于快照发布与审计。
2. 健康检查
| 方法 | 路由 | 鉴权 | 说明 |
|---|---|---|---|
GET |
/healthz |
无 | Core、Store 和后台任务健康时返回 204,否则返回 503 |
3. Responses 与模型目录
Responses HTTP body、WebSocket message 和 frame 不设置网关私有长度上限;协议可接受性由上游决定。
| 方法 | 路由 | 说明 |
|---|---|---|
POST |
/v1/responses |
OpenAI Responses JSON;stream=true 返回 SSE,否则返回完整 JSON |
GET |
/v1/responses |
通过 HTTP Upgrade 建立 Responses WebSocket |
POST |
/v1/responses/review |
使用同一 Responses 合同发起 review 子代理请求 |
GET |
/v1/models |
返回当前 Client Key 账号范围内各 Provider 的可用公开模型并集;有两种响应形态,见下 |
GET |
/v1/models/catalog |
返回 Codex 客户端使用的模型目录 |
GET |
/v1/models/{model_id}/info |
返回 Codex 客户端使用的单模型信息 |
GET |
/v1/models/{model_id} |
返回 OpenAI 兼容的单模型详情 |
GET /v1/models 默认返回 OpenAI 兼容列表 {"object": "list", "data": [...]};请求携带非空
client_version query 参数(Codex 客户端)时改为返回 Codex 专用目录合同 {"models": [...]}。
OpenAI 路径保留客户端 Responses wire 语义:请求 body 的未知字段和字段顺序保持不变(受控模型 映射除外),HTTP SSE 与 WebSocket 的上游业务事件字节原样转发,response ID 按 opaque 值处理而不 假设 UUID 或固定长度;OpenAI 上游错误 envelope 和允许下发的 opaque header 值也不由 canonical 观测结果重写。xAI 是 Grok wire 与 Responses wire 之间的协议转换层,转换只在 xAI Provider 内完成。 上游结构化错误的 message/code/type 会透传给客户端,其中内嵌的账号指纹 UUID 已脱敏。模型映射是 全局精确映射,未命中时模型名原样交给候选 Provider;分组只限定账号集合,不参与模型改名。
4. 管理员认证
| 方法 | 路由 | 请求 | 说明 |
|---|---|---|---|
POST |
/api/admin/auth/login |
{ username?, password } |
创建管理员会话并设置 Cookie |
GET |
/api/admin/auth/status |
无 | 返回当前 Cookie 是否已认证 |
POST |
/api/admin/auth/logout |
无 | 删除当前会话并清除 Cookie |
5. 账号
账号 API 使用统一路由,不存在 Provider Instance 或 Provider 专属账号路由。需要 Provider 的请求只接受
provider: "openai" | "xai"。
| 方法 | 路由 | 主要 query/body | 说明 |
|---|---|---|---|
GET |
/api/admin/accounts |
page、pageSize、provider、groupId、search、status、排序字段 |
分页查询账号与汇总 |
GET |
/api/admin/accounts/detail |
accountId |
查询账号详情、额度和本地用量 |
GET |
/api/admin/accounts/export |
accountIds、confirm=export_sensitive_accounts |
显式导出最多 200 个账号的敏感 Provider 文档 |
POST |
/api/admin/accounts/import |
{ provider, data } |
导入或按上游身份更新账号;新账号保持未分组,已有账号保留所属分组 |
POST |
/api/admin/accounts/refresh |
{ accountId } |
手工刷新 OAuth credential(idToken / accessToken / refreshToken),不刷新额度 |
POST |
/api/admin/accounts/rotate |
OpenAI rotation 字段 | 手工替换 OpenAI OAuth token |
POST |
/api/admin/accounts/update |
{ accountId, enabled, concurrencyLimit, weight, groupIds } |
一次更新账号调度状态、并发上限(null 表示继承运行参数)、权重(1–100)与所属分组 |
POST |
/api/admin/accounts/batch-update |
{ accountIds, enabled, concurrencyLimit, weight, groupIds } |
一次事务统一更新所选账号的全部调度字段与完整分组集合 |
POST |
/api/admin/accounts/delete |
{ provider, accountIds } |
批量删除 1–200 个账号 |
GET |
/api/admin/accounts/quota |
accountId |
读取当前额度,不强制访问上游 |
POST |
/api/admin/accounts/quota/refresh |
{ accountId } |
访问 Provider 并刷新额度,同时同步额度所属状态 |
GET |
/api/admin/accounts/models |
accountId |
优先读取该 Provider + 套餐的模型 cache,缺失时有限实时拉取 |
POST |
/api/admin/accounts/models/refresh |
{ accountId } |
强制拉取最新模型并覆盖 cache |
GET |
/api/admin/accounts/connection-test |
accountId、modelId |
通过 SSE 返回实时连接测试事件,不作为业务 Responses 用量记录 |
POST |
/api/admin/accounts/oauth/start |
{ provider, name, accountId? } |
创建 OpenAI 或 xAI OAuth flow;accountId 表示重新授权 |
POST |
/api/admin/accounts/oauth/complete |
{ provider, flowId, callbackUrl } |
消费 OAuth callback;新账号保持未分组,重新授权保留所属分组 |
账号列表支持以下稳定值:
provider:all、openai、xai;groupId: 分组 ID、ungrouped,或省略以不过滤;status:normal、quota_exhausted、rate_limited、disabled、error;sortBy:email、status、planType、usage、lastUsedAt、expiresAt;sortDirection:asc、desc。
导入的 data 必须是 JSON object,内部 schema 由目标 Provider 独占解释:
- OpenAI 接受 OAuth 账号文档、账号 bundle 和 Agent Identity;
- xAI 从单账号 object 或
accounts数组中提取 OAuth token;包装中的代理、并发、优先级等字段不参与认证; - xAI 批量导入逐条独立校验:失败条目跳过并记录日志,不中断其余条目,仅当没有任何条目成功时整个导入才报错;
- xAI API Key 不是受支持的账号 credential;
- 导入不会只凭文件外形写入账号;目标 Provider 使用认证材料完成必要的 token exchange 或已认证账号资料补全。
账号导入与 OAuth complete 不接收 groupIds。首次创建的账号保持未分组;按既有上游身份重新导入、
重新授权以及普通 credential refresh/rotation 均保留已有分组。分组关系只通过账号编辑维护。
账号列表的每个 item 返回轻量 groups: [{ id, name, enabled }]。
OpenAI 的 CPR 导出保持 OAuth 账号的既有字段;Agent Identity 账号则输出
authMode: "agentIdentity"、agentRuntimeId、agentPrivateKey 和可选的
taskId,不伪造 OAuth token 或过期时间。一个 sourceFormat: "cpr" 文档可以包含两种
认证类型,导入时按每个账号的认证模式分别解析。
{
"sourceFormat": "cpr",
"accounts": [{
"id": "acct_...",
"accountId": "...",
"userId": "...",
"authMode": "agentIdentity",
"agentRuntimeId": "...",
"agentPrivateKey": "...",
"taskId": "..."
}]
}
OpenAI rotation 请求字段为:
{
"provider": "openai",
"accountId": "acct_...",
"idToken": "...",
"accessToken": "...",
"refreshToken": "..."
}
OAuth start 使用:
{
"provider": "openai",
"name": "account name",
"accountId": null
}
重新授权已有账号时,start 请求仍携带 provider 和展示用 name,只额外提供目标 accountId;
客户端不得提交 credentialRevision、旧 token 身份或其他并发控制字段。complete 请求也不重复提交
accountId,后端通过 flowId 中保存的目标绑定完成授权。
OpenAI 身份、额度与状态
- OAuth 文件导入的 canonical 凭据字段为
accessToken、refreshToken、idToken,不接受含义模糊的token。仅有 refresh token 时先换取 access token。 - 身份补全复用官方
token_data.rs::parse_chatgpt_jwt_claims:优先解析idToken,缺失字段再由accessToken补齐;email优先 JWT 顶层值、其次https://api.openai.com/profile.email,用户 ID 优先chatgpt_user_id、其次user_id。该路径不调用whoami,也不信任导入文档顶层的userId/accountId。 - 首次 OAuth 保留回调
state、PKCE 与官方 token exchange,并持久化idToken、accessToken、refreshToken。刷新响应中的三个 token 字段均按官方语义独立轮换:返回新值时替换,省略时分别保留 现值。重新授权也保留这些回调保护,但只轮换目标账号的 token。回调地址只承载code/state, 不以 host/path 形式作为拒绝条件。 - 账号文件导入和首次 OAuth 创建在 credential 提交后立即尝试一次额度观测。观测失败只记录告警, 不回滚已提交的账号;重新授权和手工或后台 RT 刷新只更新 token,不隐式等同于手工额度刷新,也不更新 既有账号资料或 OAuth principal。
- OAuth pending flow 先取得带过期时间的独占 claim,只有账号事务提交成功后才消费。失败会释放 claim, 但上游 authorization code 本身通常只能交换一次;已完成过 token exchange 时应重新创建 OAuth flow。
GET /accounts/quota只读取最后一次落库快照;POST /accounts/quota/refresh才访问上游。access token 已过期时,额度刷新要求先走 credential 刷新或重新授权,不会拿过期 token 探测额度。- 成功额度观测会 revision-fenced 写入 quota;明确
Allowed投影为normal,明确耗尽投影为quota_exhausted。额度观测不会清除凭据过期、无效或封禁事实;这些事实统一投影为error,并由errorReason区分。额度接口的 401/403 也不足以判定 refresh token 永久失效,credential 终态只由 OAuth refresh 的明确永久错误写入。 - 正常 Responses 请求会解析上游响应的 rate-limit headers,合并进同一 quota 快照并同步状态。Free、 K12 等套餐共用该状态机;套餐只参与账号展示和按套餐隔离的模型目录 cache,不存在 K12 专属额度路径。
- 账号展开区的 Token 结构和模型排行使用代表性账号级额度窗口聚合,查询边界严格为
[resetAt - windowSeconds, resetAt);额度刷新若返回了更早的重置时间,会按新边界重新聚合。无法取得 完整窗口边界或只有模型专属额度时显示无数据,不回退成历史累计。金额原值保持完整精度,USD 展示值 小于 1 美元时最多保留四位小数,其余保留两位。 - 账号页没有定时静默轮询。手工额度刷新只替换响应中的账号行并同步状态汇总,不触发整页 loading;若 新状态不符合当前筛选,该行从当前页移除。请求驱动或后台任务产生的状态变化,需要下一次显式查询账号 列表后才会显示。
6. 账号分组
分组是 Provider-neutral 的账号集合;一个组可包含任意 Provider 账号,一个账号也可属于多个组。
| 方法 | 路由 | 主要 query/body | 说明 |
|---|---|---|---|
GET |
/api/admin/account-groups |
page、pageSize、search、enabled |
分页查询分组;返回账号可用性、并发槽位(Redis 不可用时 usedSlots=null)及成功请求 USD 用量 |
POST |
/api/admin/account-groups/create |
{ name, description, color } |
创建空分组;color 严格为 #RRGGBBAA,返回时统一大写 |
POST |
/api/admin/account-groups/update |
{ id, name, description, color } |
更新名称、描述和颜色 |
POST |
/api/admin/account-groups/enable |
{ id } |
启用 |
POST |
/api/admin/account-groups/disable |
{ id } |
禁用;已绑定 Key 保持受限,不回退到全部账号 |
POST |
/api/admin/account-groups/delete |
{ id } |
删除未被 Client Key 引用的组 |
GET |
/api/admin/account-groups/members |
id |
查询跨 Provider 成员 |
列表数据为 { items, page, configRevision },其中 item 返回 memberCount、按 Provider 聚合的
providerCounts 和 clientKeyCount。成员响应为 { id, items, total, configRevision };成员的
providerKind 描述账号自身 Provider,并不是分组属性。
7. Client Key
| 方法 | 路由 | 主要 query/body | 说明 |
|---|---|---|---|
GET |
/api/admin/client-keys |
cursor、limit、search、sortBy、sortDirection |
游标分页查询 |
POST |
/api/admin/client-keys/create |
创建字段 | 创建带账号范围的 Client Key |
GET |
/api/admin/client-keys/reveal |
id |
显式读取完整明文 Key |
POST |
/api/admin/client-keys/update |
更新字段 | 原子更新名称、分组范围和限额 |
POST |
/api/admin/client-keys/enable |
{ id } |
启用 |
POST |
/api/admin/client-keys/disable |
{ id } |
禁用 |
POST |
/api/admin/client-keys/delete |
{ id } |
删除 |
创建字段为 name、可选 label、groupIds、maxConcurrency、requestsPerMinute,更新请求再增加
id。groupIds 必须显式提交:空数组派生 routingScope: "all",非空数组派生
routingScope: "groups"。响应同时返回分组引用 groups,以及从当前有效账号池派生、仅供展示的
providerKinds;Client Key 不再保存 providerKind。创建和 reveal 响应会返回完整明文 Key,调用方
必须立即安全保存。
8. 运行设置
| 方法 | 路由 | 说明 |
|---|---|---|
GET |
/api/admin/settings |
读取运行设置 |
POST |
/api/admin/settings/update |
原子替换全部运行设置 |
GET |
/api/admin/settings/admin-api-key |
只返回管理 API Key 是否存在 |
POST |
/api/admin/settings/admin-api-key/delete |
删除管理 API Key |
POST |
/api/admin/settings/admin-api-key/regenerate |
重新生成并一次性返回完整管理 API Key |
设置更新字段包括:
modelMappings
refreshMarginSeconds
refreshConcurrency
maxConcurrentPerAccount
requestIntervalMs
rotationStrategy
usageRetentionDays
opsEventRetentionDays
auditRetentionDays
rotationStrategy 可取 smart、quota_reset_priority、round_robin、sticky。
9. 备份
全部备份端点位于 /api/admin/settings/backups/*,内部由独立 BackupService 承担,不并入设置用例。响应继续使用 AdminEnvelope,wire 字段 camelCase,Cache-Control: no-store。
| 方法 | 路由 | 请求 | 说明 |
|---|---|---|---|
GET |
/api/admin/settings/backups |
无 | 读取存储配置(含明文 Secret)、验证状态与调度配置 |
POST |
/api/admin/settings/backups/storage/update |
S3 配置 | 更新存储配置;secretAccessKey 为空字符串会校验失败 |
POST |
/api/admin/settings/backups/storage/test |
无 | 测试已保存的存储配置(Put/Head/Get/Delete 探针) |
POST |
/api/admin/settings/backups/schedule/update |
调度配置 | 更新 Cron、时区与保留策略 |
GET |
/api/admin/settings/backups/records |
查询参数 | 分页查询备份记录 |
POST |
/api/admin/settings/backups/create |
{ expiresInDays? } |
创建手动备份,返回 202 Accepted;expiresInDays 为过期天数(0 或缺省表示不过期) |
POST |
/api/admin/settings/backups/download-url |
{ backupId } |
创建 5 分钟有效预签名下载地址(仅 completed) |
POST |
/api/admin/settings/backups/delete |
{ backupId } |
请求删除(进入 deleting,由 Worker 收敛硬删除) |
读取设置响应(Secret 以明文返回,由前端掩码显示):
storageRevision, endpoint, region, bucket, accessKeyId, secretAccessKey, prefix,
forcePathStyle, verified, scheduleEnabled, cronExpression, scheduleTimezone,
retentionDays, retentionCount, nextRunAt, lastVerifiedAt, updatedAt
更新存储请求字段:
endpoint, region, bucket, accessKeyId, secretAccessKey, prefix, forcePathStyle
secretAccessKey 为空字符串会校验失败;由于 GET 会回传已保存的明文 Secret,保存时始终整体提交当前值。已有备份记录时,endpoint/region/bucket/forcePathStyle 不允许变化(存储身份锁定,409);只允许轮换凭据与修改 prefix。
更新调度请求字段:
scheduleEnabled, cronExpression, scheduleTimezone, retentionDays, retentionCount
cronExpression 为 5 段格式;retentionDays/retentionCount 为 0 表示禁用对应清理。启用计划前必须已保存完整存储配置且通过连接测试。
记录列表查询参数:
page, pageSize, status, trigger
status 可取 queued/dumping/uploading/completed/failed/deleting;trigger 可取 manual/scheduled。记录响应字段:
id, triggerKind, status, scheduledAt, objectKey, sizeBytes, sha256, attemptCount,
errorCode, errorMessage, startedAt, completedAt, expiresAt, createdAt, updatedAt
expiresAt 为创建时确定的手动过期时间;到期后由 Worker 自动清理(手动与计划备份均可设置)。
连接测试响应:
{ ok, stage, code, message }
stage 为 putObject/headObject/getObject/deleteObject。探测成功后以 storageRevision CAS 写入 lastVerifiedAt;测试期间配置变化则丢弃结果。
备份错误映射(AdminErrorCode 既有体系):
| HTTP | 场景 |
|---|---|
400 |
配置、Cron、时区或状态参数无效 |
404 |
备份记录不存在 |
409 |
已有活跃任务、状态冲突或存储身份锁定 |
502 |
S3 兼容服务返回无效或失败响应 |
503 |
PostgreSQL、pg_dump 或对象存储暂不可用 |
审计动作:backup.s3_config_updated、backup.s3_connection_tested、backup.schedule_updated、backup.created、backup.download_url_created、backup.delete_requested。审计详情与记录表均不保存 Secret、数据库连接串或预签名 URL query。
10. Dashboard、用量与错误
| 方法 | 路由 | 说明 |
|---|---|---|
GET |
/api/admin/dashboard/summary |
Dashboard 汇总;支持 kind、startTime、endTime |
GET |
/api/admin/dashboard/trend |
Dashboard 趋势;`kind=usage |
GET |
/api/admin/usage/records |
请求记录分页列表 |
GET |
/api/admin/usage/records/detail |
按 id 查询请求详情 |
GET |
/api/admin/usage/records/summary |
当前筛选条件的请求汇总 |
GET |
/api/admin/usage/insights/overview |
用量、成本与成功率洞察 |
GET |
/api/admin/usage/insights/diagnostics |
按维度聚合诊断 |
GET |
/api/admin/operations/errors |
运维错误分页列表 |
用量查询可组合页码/游标、时间范围、Provider、Client Key、账号、模型、route、transport、状态码、
request/response/upstream ID、outcome 与搜索文本。诊断 dimension 可取 model、account、
apiKey、provider、transport、failureClass、status。
汇总与洞察中的请求数与 outcome 分布覆盖筛选范围内全部请求;token、缓存、延迟与成本聚合仅统计 已完整交付客户端的成功响应。
OpenAI 的 serviceTier 只接受上游响应生命周期事件确认的实际 response.service_tier;请求里的
期望档位只保留在 request summary,不能冒充响应事实。计费展示把 priority/fast 映射为 Fast,
flex 映射为 Flex,缺失或 default 映射为 Default;未知非空值原样展示。Fast 优先使用模型的
priority 价格,缺少专用价格时回退到标准价格的 2.00x;Flex 为 0.50x,Default 为 1.00x。
11. 版本、更新与重启
| 方法 | 路由 | 主要 query/body | 说明 |
|---|---|---|---|
GET |
/api/admin/system/version |
无 | 当前构建、部署模式和可用更新 |
GET |
/api/admin/system/update/detail |
`refresh=true | false` |
GET |
/api/admin/system/update/events |
无 | SSE 更新事件流 |
POST |
/api/admin/system/update |
可选 { targetVersion } |
开始在线更新 |
GET |
/api/admin/system/update/status |
无 | 查询当前更新或回滚状态 |
POST |
/api/admin/system/rollback |
无 | 回滚到保留的上一版本 |
POST |
/api/admin/system/restart |
无 | 请求进程重启 |
在线更新仅在当前部署模式、Release 资产和进程重启能力都满足要求时可用,且只在同一 major 版本内
提供:跨大版本目标会以 40901 冲突拒绝,需按发布说明手动迁移。仓库发版流程见
部署文档 与根目录 README。