18 KiB
HTTP API 模块
1. 定位
本模块冻结 cpa-ext 的 HTTP 边界:谁可以调用、使用哪些固定路径、请求/响应如何表达、错误是否稳定,以及 UI 如何只通过公开用例访问 Core。
API 不直接暴露 Repository 表,也不允许 UI 自行拼 SQL 语义。每个写接口都对应 core.md 中的应用用例,每个查询都经过权限化 facade。
当前宿主基线为 CLIProxyAPI v7.2.130:
- 插件 Management routes 位于
/v0/management/...,由 CPA Management Key 保护; - plugin resource 位于
/v0/resource/plugins/<pluginID>/...,宿主只支持未鉴权的精确 GET; - 插件声明的路径不能包含
:,*或..; - Management 和 resource handler 都会在进入插件前被宿主完整读入内存;
home.enabled时两类插件路由均不可用;- Management JSON 的所有字符串目前会被宿主 HTML entity 转义,resource JSON 不会。
这些是设计约束,不是实现细节。
2. API 表面
2.1 管理 API
固定基路径:
/v0/management/plugins/cpa-ext/v1
CPA 先执行 Management 鉴权,再把请求交给插件。cpa-ext 不创建第二套管理员密码或 session。第一版 CPA 只有一把 Management Key,因此所有 Management 调用都是全管理员权限,不能在文档中虚构细粒度 RBAC。
2.2 Browser resources
固定基路径:
/v0/resource/plugins/cpa-ext
用途分为:
- 静态 UI shell 和构建产物;
- 最小公开 readiness;
- 由插件自行校验 downstream Key 的用户只读 JSON。
resource route 在宿主层没有鉴权。任何包含管理员数据、写操作、OAuth 原文或数据库备份的 handler 都不得注册为 ResourceRoute。
2.3 模型调用 API
模型调用仍使用 CPA 原生 Codex/OpenAI Responses 路径。cpa-ext 通过 frontend auth、interceptor、scheduler 和 response/usage callback 参与,不另建代理入口。允许路径见 access-routing.md。
3. 通用 wire 约定
3.1 JSON
- Content-Type:
application/json; charset=utf-8; - 字段名:
snake_case; - 未知写入字段默认拒绝;
- 空集合返回
[]/{},不使用null; - ID 是 opaque string,调用方不能解析前缀获得权限;
- 时间是 UTC RFC3339Nano;
- 时长使用整数毫秒或明确命名的秒数;
- 百分比使用
0..100并明确字段名,fraction 使用0..1; - Token 只出现在管理员审计 DTO;
- secrets、Authorization、Prompt、Response 和 raw Auth JSON 永不进入普通 DTO。
3.2 金额
{
"currency": "USD",
"micros": 1234567,
"display": "$1.234567"
}
currency + micros 是机器真值,display 由服务端统一 formatter 生成。用户看到的余额、额度、消费和账单只有金额,不返回 credits/token 余额。管理员可以在账单明细中额外看到 Token、费率和倍率。
3.3 成功响应
单对象:
{
"data": {},
"meta": {
"request_id": "req_opaque",
"revision": 12
}
}
列表:
{
"data": [],
"meta": {
"request_id": "req_opaque",
"next_cursor": "opaque_cursor",
"has_more": false
}
}
创建 Credential/轮换 secret 的第一次成功响应额外包含 secret,并强制 Cache-Control: no-store。明文只展示一次。
3.4 错误响应
{
"error": {
"code": "billing_quota_exhausted",
"message": "Amount balance is exhausted.",
"request_id": "req_opaque",
"retryable": false,
"details": {
"field": "billing_account_id"
}
}
}
message 可本地化且不得作为程序判断依据;code 稳定。details 只包含安全字段、约束和资源 ID,不回显 secret 或原始数据库错误。
状态映射:
| HTTP | 语义 |
|---|---|
| 400 | DTO/字段/游标无效 |
| 401 | 用户 Key 无效;当前 CPA frontend auth 也会把认证依赖故障折叠为 401 |
| 403 | 已认证但无权限/入口不允许 |
| 404 | 资源不存在或为防枚举而隐藏 |
| 409 | revision、状态或幂等冲突 |
| 412 | expected_revision 不匹配 |
| 413 | 请求体过大;真正上限必须先由反向代理/CPA HTTP 层执行 |
| 422 | 语法正确但领域规则不可满足 |
| 429 | 金额额度、并发或速率限制 |
| 500 | 未分类内部缺陷,响应脱敏 |
| 503 | 数据库、账本、价格或必需 runtime 不健康 |
3.5 版本与并发控制
- 路径版本为
v1; - 响应头返回
X-CPA-Ext-API-Version: 1; - 每个可写对象返回
revision; - PATCH/PUT/状态动作必须携带
expected_revision; - 版本不匹配返回
412 revision_mismatch和当前 revision; - 不允许 last-write-wins 覆盖套餐、价格、路由和账户状态。
3.6 幂等
以下写操作必须携带 Idempotency-Key:
- provision/签发/轮换 Credential;
- 金额 adjustment;
- 价格发布/回滚;
- quota 批量刷新;
- backup/rebuild 等 maintenance command。
相同 key + 相同 payload 返回同一业务结果;相同 key + 不同 payload 返回 409 idempotency_conflict。
Credential 明文不持久化,因此签发/轮换响应重放只保证不会再创建第二个 secret:首次响应有 secret,后续幂等重放返回同一 Credential 但 secret=null、secret_available=false。如果调用方丢失首次响应,只能执行轮换。
4. 管理 API 路由
CPA 只允许精确路径,所以记录 ID 放 query/body,不使用 /credentials/:id。
4.1 系统与诊断
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /status |
版本、runtime topology、schema/capabilities、数据库与统计状态 |
| GET | /readiness |
计费依赖、HMAC keyring、active price、gateway 前置条件 |
| GET | /diagnostics |
脱敏故障、lag、unpriced/unmeasured/persistence gap |
readiness 只能证明插件自身依赖。部署网关还必须验证 CPA 中插件 active、Home 关闭、exclusive/sentinel 流量探针成立,不能只信一个自报 JSON。
4.2 下游账户、Credential 与套餐
| Method | Path suffix | 用途 |
|---|---|---|
| POST | /provision |
原子创建 BillingAccount、绑定 Plan/Route 并签发第一把 Key |
| GET | /billing-accounts |
列表或 ?id= 详情 |
| POST | /billing-accounts |
创建空账户 |
| PATCH | /billing-accounts |
修改 label/status/并发等安全字段 |
| GET | /credentials |
列表或 ?id= 详情,不返回明文/digest |
| POST | /credentials |
为现有账户签发 Key |
| PATCH | /credentials |
label、有效期、模型/tier 范围 |
| POST | /credentials/rotate |
创建新 SecretVersion 并按策略失效旧版本 |
| POST | /credentials/revoke |
撤销 Credential/SecretVersion |
| GET | /plans |
计划列表或详情 |
| POST | /plans |
创建计划 |
| PATCH | /plans |
修改计划,必须预览影响 |
| DELETE | /plans |
retire 未使用计划;历史引用不删除 |
| GET | /route-policies |
查询账户/credential 路由策略 |
| PUT | /route-policies |
原子替换一条策略及绑定 |
POST /provision 是 UI 的主路径,避免业务层出现“账户创建成功但 Key/Plan/Route 只完成一半”。其事务边界以 core.md 为准。
4.3 金额账本
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /ledger |
按 account、时间、kind 稳定分页 |
| POST | /ledger/adjustments |
管理员充值、扣减、refund/纠错 |
| GET | /billing-cycles |
周期、额度、消费、余额投影 |
Adjustment 必须包含 billing_account_id、Money、kind、reason、expected_revision 和 Idempotency-Key。API 不提供“直接改 balance”端点。
4.4 价格
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /pricing/active |
active version 与模型政策 |
| GET | /pricing/versions |
版本历史/详情 |
| GET | /pricing/candidates |
候选、来源和冲突 |
| POST | /pricing/candidates/refresh |
拉取外部 candidate,不发布 |
| POST | /pricing/preview |
校验 draft、diff、golden cases |
| POST | /pricing/publish |
显式发布不可变版本 |
| POST | /pricing/rollback |
以历史内容创建并发布新版本 |
没有“直接 PUT 当前价格”的捷径。所有变更走 draft/preview/publish,详见 pricing.md。
4.5 上游账户和池
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /upstream-accounts |
目录、状态、priority、bindability |
| POST | /upstream-accounts/sync |
触发 host.auth.list reconciliation |
| POST | /upstream-accounts/reconcile |
人工确认 Auth replacement/merge 拒绝 |
| GET | /upstream-quotas |
最新 quota snapshot |
| POST | /upstream-quotas/refresh |
单个/有界批量按需刷新 |
| GET | /upstream-pools |
池列表/详情 |
| POST | /upstream-pools |
创建池 |
| PATCH | /upstream-pools |
修改名称/成员/revision |
| DELETE | /upstream-pools |
retire 无引用池 |
Quota refresh 请求只接受内部 upstream_account_id[],不接受 URL、Header、token 或任意 provider payload。
4.6 统计、请求与审计
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /statistics/overview |
金额/请求/健康总览 |
| GET | /statistics/activity |
时间序列 |
| GET | /statistics/analysis |
维度分析 |
| GET | /requests |
Request 列表或 ?request_id= 详情 |
| GET | /executions |
Execution/重试明细 |
| GET | /audit-events |
管理动作与安全事件 |
图表接口返回服务端聚合。请求详情默认不含 Prompt/Response;CPA request log 访问走单独显式授权、短期 token、审计和脱敏流程。
4.7 Maintenance
| Method | Path suffix | 用途 |
|---|---|---|
| GET | /maintenance/status |
checkpoint、备份、完整性和 runner 状态 |
| POST | /maintenance/backup |
创建 SQLite online backup |
| GET | /maintenance/backups |
脱敏备份清单,不直接返回任意文件路径 |
| POST | /maintenance/integrity-check |
启动有界检查 |
| POST | /maintenance/rebuild |
从指定 event_seq 重建投影 |
在线 API 不提供 restore、删除数据库或任意路径下载。恢复属于 operations.md 的离线、排空流程。
5. 用户只读 API
固定 ResourceRoute:
| Method | Path | 用途 |
|---|---|---|
| GET | /v0/resource/plugins/cpa-ext/v1/me |
当前 Credential、共享金额账户和周期摘要 |
| GET | /v0/resource/plugins/cpa-ext/v1/me/requests |
当前 Credential 的逐请求金额记录 |
| GET | /v0/resource/plugins/cpa-ext/v1/me/activity |
当前 Credential/账户金额趋势 |
鉴权:
Authorization: Bearer <downstream-key>
规则:
- Key 不得放 query、fragment、cookie、URL 或 localStorage;
- 插件自行 HMAC lookup,并按 Credential scope 构造查询;
- 共享 BillingAccount 的余额可以显示,但默认请求列表只显示当前 Credential 产生的记录;
- 请求参数中的 account/credential ID 不能扩大 scope;
- 响应
Cache-Control: no-store、Pragma: no-cache、Vary: Authorization; - 错误不区分不存在、撤销、过期等可枚举细节;
- 用户接口只返回 Money、模型展示名、时间和安全状态,不返回 Token、上游账户、价格规则或管理员诊断。
当前 resource 只有 GET,且没有 HttpOnly session/CSRF 模型。需要用户写操作、稳定登录或团队账号时必须增加 sidecar/public API 或扩展 CPA authenticated user plugin routes。
6. UI resources
至少注册:
GET /v0/resource/plugins/cpa-ext/ui
GET /v0/resource/plugins/cpa-ext/assets/<每个确定文件名>
GET /v0/resource/plugins/cpa-ext/ready
宿主不支持 wildcard,因此每个构建产物在 management.register 中逐个声明。前端使用相对 base、HashRouter、不注册 service worker。
/ready 只返回:
{"ready":true,"version":"0.1.0"}
它不包含数据库路径、price 内容、账户数、错误详情或 secret。外部 gateway 可以把 404/非 200 视为插件缺失,但仍需执行部署级旁路探针。
7. 查询、分页和导出
- 默认
limit=50,最大200; - 使用
(occurred_at, stable_id)编码的 opaque cursor,不使用大 offset; - cursor 绑定 endpoint、scope、排序和过滤条件,修改条件后 cursor 无效;
- 默认时间范围 24 小时,普通交互最大 90 天;
- 过滤字段使用 allowlist,未知字段返回 400;
- 排序字段固定,禁止任意 SQL column;
- 大导出使用受控异步任务/sidecar,不能在动态库 handler 中一次加载全表;
- CSV 需要防
=,+,-,@公式注入,并记录导出审计。
8. 输入与资源限制
插件 handler 内执行:
- 方法和精确路径二次校验;
- Content-Type 校验;
- strict JSON + 单文档 EOF;
- 字段长度、集合数量、时间范围和分页上限;
- 响应行数/字节预算;
- context cancellation;
- 不持锁执行 SQL、host callback 或网络。
但 CPA 当前在调用 handler 前执行 io.ReadAll,所以这些校验无法保护宿主免受超大 body。生产反向代理/CPA HTTP 层必须先限制:
- Management body 建议不超过 1 MiB;
- Resource GET 不接收 body;
- Header/query 总大小由入口代理限制;
- 请求读取超时和并发连接数在入口层限制。
9. 当前 CPA Management JSON 转义限制
internal/pluginhost/management.go 会对看起来像 JSON 的响应递归执行 html.EscapeString。因此 A & B <x> 在 wire 上会变成 HTML entity;这不是标准 JSON API 应有的语义。
过渡规则:
- cpa-ext Management JSON 响应头标记
X-CPA-Ext-String-Encoding: html-entity-v1; - 内置 UI 对响应字符串最多兼容 decode 一次,再通过 React text node 渲染;
- 禁止
dangerouslySetInnerHTML; - IDs、error codes、cursor 只使用不受影响的安全字符;
- 写 DTO 不能盲目回传未 decode 的读 DTO;
- 测试覆盖
A & B <x>往返和已含 entity 文本; - 在 CPA 停止改写 JSON 或 API 移到 sidecar 前,不将 Management API 宣称为无损的通用第三方 API。
Resource JSON 不经过该 escape 路径,仍然必须使用普通 JSON encoder 和文本渲染防 XSS。
10. 安全头与浏览器策略
HTML:
Content-Security-Policy,默认default-src 'self',按实际构建最小放开;X-Content-Type-Options: nosniff;Referrer-Policy: no-referrer;Cache-Control: no-store(HTML shell);- 不使用 inline script,或固定构建 hash;
- 不信任 iframe parent 消息,除非校验明确 origin。
敏感 JSON:
Cache-Control: no-store;- 默认不启用 CORS;
- 不在错误响应反射 Origin/Header/Body;
- 不把 Management Key 或 downstream Key 写入前端持久存储。
完整要求见 security.md。
11. 审计
以下写操作必须形成审计事件:
- provision、签发、轮换、撤销;
- 账户/套餐/路由/池修改;
- 金额 adjustment/refund;
- 价格发布/回滚;
- 上游 Auth reconciliation;
- quota reset credits 等有副作用 provider 操作;
- backup、rebuild、导出;
- 安全设置和热配置拒绝。
CPA 当前只有共享 Management Key,Actor 至少记录 management principal fingerprint、来源 IP 的受信代理结果、request ID、reason 和变更前后 revision。调用方自报 actor 只能作为 label,除非受信 gateway 已验证并覆盖该 header。
12. 如何参考现有项目
12.1 CLIProxyAPI
sdk/pluginapi/types.go:ManagementRequest/Response、Route/Resource;internal/pluginhost/management.go:精确路径、未鉴权 resource、body ReadAll、JSON escape;internal/api/server_management.go:Management middleware、Home 限制和 route mounting;examples/plugin/management-api/:ABI/RPC 形状。
12.2 cpa-plugin-key-billing
internal/plugin/management.go:精确 route 表、query/body 传 ID、严格 DTO;internal/plugin/admin.go:价格、计划、Key 操作用例;internal/plugin/ui.html:Management 页面调用方式。
吸收其固定路径和 handler 分派;不要继承错误响应泄露内部文本、浏览器持久化 Management Key 或 UI 直接承担业务原子性的做法。
12.3 cpa-usage-keeper
internal/api/router.go:管理员/Key viewer 分离、no-store 与静态资源;internal/api/usage_*:分页、范围和统计投影;internal/api/pricing*.go、quota.go:领域 API;internal/api/request_limits.go、errors.go:入口限制和脱敏错误;- API security tests:session、rate limit、secret redaction、CSP、request log token。
Keeper 是独立 HTTP 服务,Gin wildcard、middleware session 和后台任务接口不能假设在 CPA plugin route 中同样可用。
13. 验收标准
- 所有注册 route 都是宿主允许的精确路径且无冲突;
- Management 数据必须通过 CPA Management 鉴权;
- resource 静态页不含敏感数据,用户 JSON 独立校验 downstream Key;
- 用户不能通过 ID/query 越权读取其他 Credential/账户;
- 所有金额 DTO 使用 currency + micros + display;
- 用户 DTO 不出现 Token、价格规则和上游账户;
- 写接口 strict decode、revision check、审计和必要幂等全部生效;
- secret 只在首次签发/轮换响应显示,重放不创建第二把 Key;
- 超大 body 在反向代理层被拒绝,不能依赖插件收到后再拒绝;
- Management JSON entity 转义兼容测试通过,且限制被明确标记;
- 列表 cursor 在并发插入下无重复/漏页;
- panic/DB 失败返回脱敏 5xx,不泄露 SQL、路径或 secret;
- Home 模式返回明确不支持,UI 不显示空数据假象;
- API、Core、UI 的字段和错误码保持同一份实现定义。