API 规划基线;实现时以 OpenAPI 为契约 日期:2026-08-31
- 推理面:
/v1/*,使用Authorization: Bearer <Bablo API Key>;Key 只代表 user + policy,不绑定单一 Group/Provider; - 管理面:
/api/v1/*,浏览器 Session Cookie + CSRF,RBAC 在 service/policy 层再次检查;管理员默认要求 MFA; - 运维面:
/healthz、/readyz、/metrics。不暴露 secrets;CPA management endpoint 仅内部网络且默认关闭。
所有响应带 request_id(header X-Request-ID,服务端生成并校验格式)。普通日志不记录 Authorization、Cookie、完整 Key、Prompt 或响应正文。
| 方法 | 路径 | 认证 | 说明 |
|---|---|---|---|
| GET | /v1/models |
API Key | 返回当前 Key 可访问的 public models,不泄漏其他租户/credential |
| POST | /v1/chat/completions |
API Key | OpenAI-compatible chat,支持明确声明的 stream/tools/reasoning 能力 |
| POST | /v1/responses |
API Key | OpenAI Responses 最小兼容面;能力不足时稳定返回 unsupported capability |
请求中的 model 是 public model ID。服务端执行 entitlement -> route snapshot -> scheduler;不能按 alias 直接计算价格或随机落到未知 Provider。
stream=true使用 SSE,正确返回Content-Type、终止事件和 request ID;JSON/SSE 响应均强制Cache-Control: no-store,不接受上游覆盖;- 首包前上游错误返回标准 JSON error;首包后错误发送可识别的 SSE error/终止事件,不能伪造完整成功;
- 客户端取消必须取消 context、释放 concurrency lease;若可取得 usage,仍生成 UsageEvent;无法取得时标为
estimated + reconcile_needed并按 reservation 金额结算,不静默免费; - 首个 payload 已发出后不做透明 fallback,所有尝试、UsageEvent 和 Billing settlement 状态进入 request trace。
| 方法 | 路径 | Session 要求 | 说明 |
|---|---|---|---|
| POST | /api/v1/auth/login |
无;要求可信 Origin |
email/password 登录;成功设置 Session/CSRF Cookie;已绑定 MFA 时返回 mfa_required=true 的 partial Session |
| GET | /api/v1/auth/session |
有效 Session | 返回 user ID、email、roles、expiry、MFA enabled/required,不返回 token |
| POST | /api/v1/auth/mfa/verify |
partial Session + CSRF | 接受 6 位 TOTP 或单次恢复码;成功旋转为 MFA-verified Session |
| POST | /api/v1/auth/logout |
任意有效 Session + CSRF | 撤销当前 Session 并清理 Cookie |
| POST | /api/v1/auth/logout-all |
full Session + CSRF | 撤销当前用户全部 Session |
| POST | /api/v1/auth/password |
full Session + CSRF | 校验当前密码,更新 Argon2id hash,撤销全部 Session |
| POST | /api/v1/auth/mfa/totp/bind |
full、尚未启用 MFA 的 Session + CSRF | 返回一次性 secret 与 provisioning_url,数据库仅保存 AEAD ciphertext |
| POST | /api/v1/auth/mfa/totp/confirm |
pending factor 的 full Session + CSRF | 二次 TOTP 确认、启用 factor、返回一次性恢复码并旋转 Session |
| POST | /api/v1/auth/mfa/recovery/regenerate |
MFA-verified Session + CSRF | 原子作废旧恢复码并返回新码一次 |
| POST | /api/v1/admin/users/{user_id}/password |
admin + MFA-verified Session + CSRF | 管理员重置密码并撤销目标用户全部 Session |
登录请求示例:
{
"email": "user@example.com",
"password": "user-supplied password"
}成功响应中的 session 只含公开身份状态;Session/CSRF 明文仅在 Cookie 中。bablo_session 为 HttpOnly、生产 Secure、Path /api/v1;bablo_csrf Path /,前端在状态变更时复制到 X-CSRF-Token。所有 mutation 还要求 Origin 精确匹配 BABLO_WEB_ORIGIN。请求正文上限 32 KiB,未知 JSON 字段被拒绝。
认证错误使用 authentication_error + 稳定 code:invalid_credentials、invalid_session、csrf_failed、mfa_required、invalid_mfa_code、permission_denied、rate_limited、conflict。限速响应为 429 并带 Retry-After。
没有邮件基础设施时不提供伪造的邮件重置 API。可信本机运维使用:
bablo auth create-admin --email user@example.com
bablo auth reset-password --email user@example.com
密码从无回显终端或 stdin 读取,不进入命令行参数;操作更新 audit,密码重置撤销全部 Session。
GET /api/v1/me/api-keysPOST /api/v1/me/api-keysPATCH /api/v1/me/api-keys/{id}POST /api/v1/me/api-keys/{id}/rotatePOST /api/v1/me/api-keys/{id}/revokeGET /api/v1/models(已登录用户可见模型;推理面另有/v1/models)GET /api/v1/me/stats/overview、GET /api/v1/me/stats/usage、GET /api/v1/me/stats/paymentsGET /api/v1/me/stats/requests/{request_id}/trace(仅当前用户)GET /api/v1/me/wallet、GET /api/v1/me/wallet/ledger(Billing 领域服务已实现;HTTP surface 后置到 user/admin 阶段,当前未挂载)GET /api/v1/me/payment-orders/{order_no}(已实现,只返回当前用户订单)
以上 Key 管理接口只接受现有 Web Session:所有方法要求 full session,POST/PATCH 继续要求精确 Origin、CSRF Cookie 与 X-CSRF-Token。用户查询和修改条件始终包含 session user ID;其他用户的 Key 返回 not_found。响应统一 Cache-Control: no-store。
创建请求:
{
"name": "development",
"expires_at": "2026-12-31T23:59:59Z",
"allowed_models": ["gpt-example-a", "gpt-example-b"],
"ip_allowlist": ["203.0.113.0/24", "2001:db8::/32"],
"rpm_limit": 60,
"tpm_limit": 100000,
"daily_budget_minor": 10000,
"monthly_budget_minor": 200000
}allowed_models 只接受 enabled/public/non-deleted catalog ID,可为空以建立默认拒绝且暂不可用的 Key。IP 可传 CIDR 或单 IP,服务端保存 canonical CIDR;空 allowlist 允许任意直连源。RPM/TPM 必须为正数,预算阈值不得为负。PATCH 支持同名字段;expires_at、RPM/TPM、daily/monthly budget 传 null 表示清除,allowed_models: [] 清空授权,ip_allowlist: [] 清空 IP 限制。未知字段、空 patch、过去到期时间和无效模型/IP/限额返回 400。
创建成功返回 201,轮换成功返回 200;只有这两个响应包含一次性 secret。普通 Key DTO 只含 id/name/prefix/status/expires_at/allowed_models/ip_allowlist/limits/last_used_at/timestamps/secret_version,从不含 secret 或 secret_hash。P0 rotate 原子替换同一记录,旧 Key 在事务提交后立即无效;revoke 幂等并立即无效。
稳定错误 code 包括 invalid_api_key(401)、ip_not_allowed/model_not_allowed(403)、rate_limited(429,Retry-After: 60)、rate_limit_unavailable(503)、insufficient_funds/budget_exceeded(402)、not_found(404)、conflict(409)和 invalid_request(400)。Key API 不提供 group_id/provider_id 绑定字段;多模型授权只通过 policy/entitlement 管理。daily/monthly budget 阈值由数据面 Billing 以已结算 charge + active/pending reservation 执行。
- 数据面顺序固定为 entitlement -> route snapshot -> scheduler selection -> resolved price snapshot -> wallet reservation -> CPA execution -> immutable UsageEvent -> wallet settlement;
max_output_tokens、max_completion_tokens、max_tokens中出现的正整数取最大值作为输出预估;显式 0、负数或非整数返回invalid_request。均未提供时,非免费模型 P0 默认预留 4096 output tokens;超过服务上限或无法精确计价返回invalid_request/price_unavailable,不会调用上游;- 非零 reservation 只接受 active/retired 且当前 effective 的 price version,并绑定实际 provider model/route/provider/credential;daily/monthly budget 和余额不足在上游前分别返回 402
budget_exceeded/insufficient_funds; - 正常完成按 UsageEvent 实际 amount settle;少于预留自动 release,多于预留补扣 available。补扣不足写 durable pending settlement/outbox,不修改历史 Usage/Ledger;
- 上游未返回完整 usage 时,UsageEvent 为
estimated=true、reconcile_needed,按 reservation 金额结算;迟到差异只能追加 reconciliation/adjustment; - 金额响应和未来 Wallet API 只使用整数
amount_minor+ 三字母 currency,不返回 float;unit_price是主货币单位/一个 token 或 request,所有维度汇总后一次向上取整到最小货币单位。
GET /api/v1/admin/users、GET /api/v1/admin/users/{id}、PATCH /api/v1/admin/users/{id};密码重置使用POST /api/v1/admin/users/{id}/password。GET /api/v1/admin/roles:返回可分配角色目录;用户角色更新随用户 PATCH 提交。GET /api/v1/models:已登录用户可见的 enabled/public 模型与 canonical capabilities/aliases;推理面的/v1/models已由 Proxy 按 API Key entitlement 返回当前 Key 可访问模型,不泄漏其他租户或 Credential。GET/POST /api/v1/admin/models、GET/PATCH /api/v1/admin/models/{id}:模型目录、别名、visibility、billing class、能力和启停;GET/POST /api/v1/admin/providers、GET/PATCH /api/v1/admin/providers/{id}:Provider 资源类型、商业政策和启停;subscription 在 P0 强制commercial_allowed=false;GET/POST /api/v1/admin/provider-models、GET/PATCH /api/v1/admin/provider-models/{id}:上游模型映射、协议、能力和审核状态;列表必须按provider_id限定;POST /api/v1/admin/providers/{id}/reconcile:提交一次完整发现快照;新增上游模型为 pending/disabled,消失只标记 discovery missing,不覆盖已批准业务配置;GET/POST /api/v1/admin/prices、GET /api/v1/admin/prices/{id}:创建 draft 价格版本并查询完整条目;scope 为 global/model/provider_model;POST /api/v1/admin/prices/{id}/activate、POST /api/v1/admin/prices/{id}/retire:发布/结束价格区间;发布后价格条目和版本身份不可修改;GET/POST /api/v1/admin/credentials:分页列出或创建 Provider Credential;创建 secret 只接受一次性请求正文,响应仅返回 descriptor,不返回值。GET/PATCH /api/v1/admin/credentials/{id}:查询或修改 region/proxy/非敏感 metadata/status;Provider、external stable ID、source kind 不可变。POST /api/v1/admin/credentials/{id}/rotate:按kind原子轮换 secret,旧版本只保留 ciphertext/history descriptor。POST /api/v1/admin/credentials/{id}/reencrypt?kind=...:按当前应用密钥重加密 active secret,保留版本历史;允许对 disabled/error Credential 执行,revoked 仅作为密钥迁移对象。GET /api/v1/admin/credentials/{id}/health:返回 last success/error class、cooldown 和 observed timestamp,不返回 secret。GET/POST /api/v1/admin/credential-pools、POST/DELETE /api/v1/admin/credential-pools/{id}/members:Provider-owned pool 与成员 priority/weight/enabled 管理;数据库拒绝跨 Provider 成员。GET/POST /api/v1/admin/routes:按model_id、opaque cursor 分页查询或创建 Route;P0 只接受exact,创建时同时提交至少一个启用 target。GET/PATCH /api/v1/admin/routes/{id}:查询 active route snapshot,或仅修改 Route metadata/enabled;已发布 version/target 不原地修改。GET/POST /api/v1/admin/routes/{id}/versions:查询 immutable version history,或关闭旧 active version 并原子发布新的 target snapshot。GET /api/v1/admin/routes/preview?model={public_id_or_alias}:管理员 dry-run;返回当前匹配的 route version 和所有 candidate target,不执行 scheduler、不触发 Credential 解密或上游请求。数据面仍必须先完成 API Key entitlement,再调用 Route resolver。GET /api/v1/admin/stats/overview?from=&to=&bucket=hour|dayGET /api/v1/admin/stats/dimensions?dimension=&from=&to=&...GET /api/v1/admin/stats/usage?...&limit=&cursor=GET /api/v1/admin/stats/scheduler-decisions?...&limit=&cursor=GET /api/v1/admin/stats/payments?from=&to=&user_id=¤cy=GET /api/v1/admin/stats/reconciliation?from=&to=&bucket=¤cy=POST /api/v1/admin/stats/rollups/rebuild(仅重建已关闭的 UTC 小时/日桶)GET /api/v1/admin/stats/requests/{request_id}/traceGET /api/v1/admin/credentials/{id}/quota?window_kind=&limit=:返回 boundedstate、按observed_at倒序的 quota snapshots 和 supported probes;window_kind为空表示全部受支持窗口,limit有界;只读、Cache-Control: no-store,不返回 Credential secret、Prompt 或响应正文。POST /api/v1/admin/wallets/{user_id}/adjustmentsGET /api/v1/admin/payment-ordersPOST /api/v1/admin/payment-orders/{order_no}/closePOST /api/v1/admin/payment-orders/{order_no}/refundGET /api/v1/admin/audit-logsGET /api/v1/admin/system
- 用户只可访问
/api/v1/me/stats/overview、/usage、/payments和自己的/requests/{request_id}/trace;管理员对应使用/api/v1/admin/stats/*。所有响应Cache-Control: no-store,错误返回稳定request_id。 - 统计筛选支持时间半开区间
[from,to)、user_id(仅管理员)、api_key_id、model(public model 或 alias)、model_id(resolved model)、provider_id、provider_model_id、credential_id、route_id、route_version_id、endpoint、status、error_class、stream和currency;列表使用有界 opaque cursor。 overview返回请求/状态、token、延迟、TTFT、TPS、429/5xx、fallback、scheduler selection、活跃用户、Usage 费用以及可安全归因的 Wallet Ledger 充值/收入/退款币种映射。多币种时不把金额相加。- Ledger 充值/收入/退款只有在筛选可直接作用于钱包事实时返回;模型、Provider、Credential 等无法归因到充值的筛选不会伪造财务分摊。Usage 消费仍按 immutable UsageEvent,并可由 Wallet Ledger 对账。
dimensions支持 global、user、api_key、model、provider、provider_model、credential、route、endpoint、status、error、stream;scheduler-decisions返回候选、排除原因、selected/fallback 和 strategy version,不包含 secret 或正文。trace汇总 request、attempt、Usage、scheduler decision、reconciliation、reservation、settlement 和 Ledger;Prompt/响应正文永不由该接口返回。reconciliation当前只接受 bucket 对齐的全局单币种切片,比较 raw Usage 与 PostgreSQL rollup,并同时核对 Usage 金额和 Ledger 财务映射;rollup 是可删除重建的投影,不是账本事实。
请求 alias 必须先由 model service 解析为 canonical public model ID,再执行 Key entitlement、route 和价格解析;alias 本身不成为独立计费维度。
模型/Provider/价格写操作均要求 Web Session、CSRF、admin RBAC 和生产 MFA;发现是信号,管理员批准/映射才会产生可路由的 provider model。价格金额使用 decimal string,unit_price 表示主货币单位/一个维度单位,缺少 input/output 或 request 必需维度时解析失败,不按 0 收费;可选 cache/reasoning 维度缺专属价格时使用基础 input/output 价格。
POST /api/v1/me/payment-orders:Web Session + CSRF;要求Idempotency-Key,请求包含正整数amount_minor、三字母 currency 和payment_provider。stripe只返回 allowlist 后的 Checkout redirect URL,不返回 secret/client secret;GET /api/v1/me/payment-orders、GET /api/v1/me/payment-orders/{order_no}:只查询当前用户订单,使用 opaque cursor;响应包含订单状态和必要 Provider 引用,不暴露 webhook body 或密钥;POST /api/v1/me/payment-vouchers/redeem:兑换一次性券码;数据库保存 hash/prefix 与短期可重放密文,成功兑换后清除密文;充值与券码消费在同一事务;POST /api/v1/admin/wallet-credits:管理员人工充值;要求 MFA/RBAC/CSRF 与Idempotency-Key,幂等事实绑定 operator、目标用户、币种和金额,跨 HTTP request ID 重试返回同一 ledger;POST /api/v1/admin/payment-vouchers、POST /api/v1/admin/payment-vouchers/{id}/revoke:创建时一次性返回明文券码;相同幂等请求在未兑换前可安全重放原码,兑换后密文被清除;POST /api/v1/admin/payment-orders/{order_no}/refund:先把用户可用余额原子移入 reserved,再以稳定 Provider idempotency key 发起退款;只有验签退款成功事件消费 reserved,确定失败事件释放,未知结果保持refund_pending并由维护 worker 对账;POST /api/v1/admin/payment-orders/{order_no}/close:先关闭 Provider 订单,再更新本地终态;Provider 调用和本地提交之间由持久 operation lease 恢复;POST /webhooks/stripe:在持久化前验证原始 body 的Stripe-Signature、timestamp tolerance、锁定 API version、Connect account/merchant 和 live/test mode;处理 Checkout paid/failed/expired、refund created/updated/failed 以及 charge dispute created/closed;- Stripe 外部退款或争议可仅携带 PaymentIntent/Charge。Adapter 通过 Provider API 恢复 Bablo order reference 后,服务端要求 order/trade/refund/payment-intent/charge 的所有已知标识一致;外部退款和 dispute lost 通过 wallet liability 回收余额,不足时设置 financial hold;dispute won 追加反向 ledger;
- 无效签名、过大 body 和超出并发槽位的 webhook 不写数据库;签名有效但订单错配/非法状态的事件保存为 rejected 审计事实并返回 Provider 不重试响应,数据库/暂态失败返回 5xx;
POST /webhooks/fixture_hmac:仅显式 opt-in 的非生产本地/集成测试;production 配置拒绝启用。
Stripe Checkout/Refund 请求使用 Bablo order number 派生的稳定 Provider idempotency key,并固定 merchant/live mode。客户端 success URL 永远不写充值账本。stripe-go/v86 v86.2.0 已接入,但在真实 Stripe test mode 完成 create -> webhook -> wallet credit -> refund/external refund/dispute -> webhook -> liability/recovery E2E 前,self-service Stripe 支付仍为 NO-GO。
{
"error": {
"type": "invalid_request|authentication_error|permission_denied|not_found|rate_limit|upstream_error|billing_error|internal_error",
"code": "stable_machine_code",
"message": "safe human-readable message",
"request_id": "req_01..."
}
}不返回内部堆栈、OAuth/token、SQL、上游完整 body。列表接口统一 cursor/limit、最大 limit、稳定排序和过滤;管理员查询必须强制作用域和时间范围,避免无界扫描。
- 管理 API 版本在
/api/v1,推理协议沿/v1;破坏性变化进入 v2; - 将 OpenAPI schema、JSON/SSE golden tests 与客户端示例同一变更更新;
/v1/messages、Gemini、WebSocket、批处理、embeddings 等不是 P0 默认承诺;每增加协议必须先确认真实客户端/CPA capability,再增加独立契约测试;- API response 只出现 Bablo 领域语义,不出现 CPA
Auth、executor.Response或 SDK config 类型。