Files
cpa-plugin/docs/modules/api.md
T

18 KiB
Raw Blame History

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-Typeapplication/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=nullsecret_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_revisionIdempotency-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/ResponseCPA 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-storePragma: no-cacheVary: 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-storeHTML 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 KeyActor 至少记录 management principal fingerprint、来源 IP 的受信代理结果、request ID、reason 和变更前后 revision。调用方自报 actor 只能作为 label,除非受信 gateway 已验证并覆盖该 header。

12. 如何参考现有项目

12.1 CLIProxyAPI

  • sdk/pluginapi/types.goManagementRequest/Response、Route/Resource
  • internal/pluginhost/management.go:精确路径、未鉴权 resource、body ReadAll、JSON escape
  • internal/api/server_management.goManagement 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.htmlManagement 页面调用方式。

吸收其固定路径和 handler 分派;不要继承错误响应泄露内部文本、浏览器持久化 Management Key 或 UI 直接承担业务原子性的做法。

12.3 cpa-usage-keeper

  • internal/api/router.go:管理员/Key viewer 分离、no-store 与静态资源;
  • internal/api/usage_*:分页、范围和统计投影;
  • internal/api/pricing*.goquota.go:领域 API
  • internal/api/request_limits.goerrors.go:入口限制和脱敏错误;
  • API security testssession、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 的字段和错误码保持同一份实现定义。