Files
codex-proxy-rs/docs/api.md
T
2026-08-27 13:27:10 +08:00

25 KiB
Raw Blame History

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_session Cookie;
  • 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。

管理写入一致性

mutation 请求不要求客户端提供全局配置版本。会改变路由快照或安全配置的写入由后端在事务内推进 内部 config_revision,并用于快照发布与审计。账号更新和分组查询/写入的部分响应会返回 configRevision 作为已提交事实,但它不是客户端 mutation 的前置条件。

2. 健康检查

方法 路由 鉴权 说明
GET /healthz 无 Core、Store 和后台任务健康时返回 204,否则返回 503

3. OpenAI 数据面与模型目录

Responses 和 Images 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 子代理请求
POST /v1/images/generations 通过 OpenAI Provider 发起图像生成;JSON 请求与响应正文原样转发
POST /v1/images/edits 通过 OpenAI Provider 发起图像编辑;JSON 请求与响应正文原样转发
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 观测结果重写。Images 请求不读取或重建 JSON,也不要求或映射模型字段;它固定使用 OpenAI Provider, 只在原始字节之外完成账号选择、鉴权头替换和端点路由,成功与失败响应正文同样保持原始字节。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/recover { accountId } 管理员显式清除该账号的本地错误/额度/cooldown 事实并重新启用,不访问上游
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/reset-credits accountId 查询 OpenAI 上游主动额度重置卡,不读取本地库存
POST /api/admin/accounts/reset-credits { accountId, creditId?, redeemRequestId } 使用 UUIDv4 幂等键消费一张 OpenAI 上游重置卡
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,Admin API 请求上限为 64 MiB;Provider 可以收紧限制, 当前 xAI 导入上限为 16 MiB。内部 schema 由目标 Provider 独占解释:

  • OpenAI 接受单账号 OAuth 文档、accounts 数组(最多 200 项)和 CPR 账号 bundle;
  • OpenAI OAuth token 字段只识别 accessToken、refreshToken、idToken,可以嵌套在账号 object 内; 每项至少包含 AT 或 RT。RT-only 会在导入时换取 AT,AT-only 不具备自动续期能力;
  • xAI 从单账号 object 或 accounts 数组中提取 OAuth token;包装中的代理、并发、优先级等字段不参与认证;
  • xAI 批量导入逐条独立校验:失败条目跳过并记录日志,不中断其余条目,仅当没有任何条目成功时整个导入才报错;
  • xAI API Key 不是受支持的账号 credential;
  • 导入不会只凭文件外形写入账号;目标 Provider 使用认证材料完成必要的 token exchange 或已认证账号资料补全。

管理端的 OpenAI AT / RT 标签是同一导入 API 的输入便利层:每行一个 token,最多 200 行,提交前 转换为对应的 accounts JSON。Admin API 本身不接收纯文本 token 列表。例如:

{
  "provider": "openai",
  "data": {
    "accounts": [
      { "accessToken": "eyJ..." },
      { "accessToken": "eyJ...", "refreshToken": "rt_...", "idToken": "eyJ..." }
    ]
  }
}

RT-only 使用同一形状,只提交 refreshToken。不得把真实 token 写入日志、issue、fixture 或文档。

账号导入与 OAuth complete 不接收 groupIds。首次创建的账号保持未分组;按既有上游身份重新导入、 重新授权以及普通 credential refresh/rotation 均保留已有分组。分组关系只通过账号编辑维护。 账号列表的每个 item 返回轻量 groups: [{ id, name, enabled }]。

OpenAI 的 CPR 导出保持 OAuth 账号的既有 token 与过期时间字段。

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 探测额度。
  • POST /accounts/recover 是管理员对本地事实的强制恢复:它清除 Redis cooldown 和已保存的额度/错误, 把账号重新启用并恢复为可调度 credential;它不验证上游账号是否已经恢复,下一次真实请求仍可重新写入 失败事实。
  • 成功额度观测会 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;若 新状态不符合当前筛选,该行从当前页移除。请求驱动或后台任务产生的状态变化,需要下一次显式查询账号 列表后才会显示。

OpenAI 主动额度重置卡

GET /api/admin/accounts/reset-credits?accountId=... 每次都查询 OpenAI 上游;后端不把卡片列表写入 PostgreSQL 或 Redis。管理端只在用户打开弹窗或点击刷新时调用,并在当前浏览器会话内缓存最近一次成功 结果,用于账号行上的 xN 提示。

查询响应:

{
  "availableCount": 1,
  "credits": [{
    "id": "credit_...",
    "status": "available",
    "title": "...",
    "expiresAt": "2026-08-31T12:00:00Z",
    "resetType": "..."
  }]
}

消费请求的 redeemRequestId 必须是小写、带连字符的 canonical UUIDv4;creditId 可省略,由上游选择 可用卡。一次请求发出后若传输结果不明确,重试必须复用完全相同的 redeemRequestId、creditId 和 账号。服务在单副本进程内按账号串行消费,并在 credential 需要刷新时以同一命令重试一次;它不会对不明 结果自动创建新消费。

{
  "accountId": "acct_...",
  "creditId": "credit_...",
  "redeemRequestId": "8fbf302d-11df-4bd5-82e4-08e4b3df7874"
}

消费响应只返回上游结果 code 和可选 credit。消费端确认成功后应重新 GET 卡片列表,并显式调用 POST /api/admin/accounts/quota/refresh 回读官方额度;不得直接改写本地 resetAt。xAI 不支持该能力。

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 引用的组

列表数据为 { 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 在创建时确定:手动备份来自 expiresInDays,计划备份来自当时的 retentionDays;到期后由 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。