第三方 API Key 集成文档(内部 · 仅 baozi 运营可访问)
baozi 第三方集成:你(第三方 / 代理 / 分销商)通过 ohoooho 统一认证(OAuth2 client_credentials 标准协议 + ES384 JWT 验签)调 baozi 接口,选套餐下单即可获得 API Key。这是 baozi 兜底产品。
鉴权实现说明:本内部文档描述 baozi 第三方 API 的全鉴权流程。第三方公开接入请用精简版 third-party-api-key.md,含 ohooho 统一认证 OAuth2 standard + partner/orders 一步下单 + 5 分钟接入。完整 M2M App 集成 + 凭证轮换 + 幂等性保证在本文档(内部)。
核心场景:OAuth2 M2M 拿 token → 选套餐 + 周期 → 调 POST /api/v1/partner/orders(一步下单+创 key)→ 一个响应里同时拿到订单 + API Key → 用 API Key 调 baozi 模型。
1. 准备工作(在你的 IDP 后台配置)
1.1 创建 API Resource
| 步骤 | 操作 |
|---|---|
| 后台 → API Resources → + Create API resource | 创 API Resource |
| API name | baozi Third-Party API(人类可读) |
| API identifier | https://baozi.ohoooho.com |
| 进刚创的 API Resource → Permissions tab → + Create permission | 加 3 个 scope |
Scope 设计:
baozi 支持 2 类 scope:
baozi 每个资源对应一个父 scope,子操作在该父 scope 下展开(GitHub repo / Slack channels 风格):
| 父 Scope 名 | 子 Scope(自动展开) | 用途 |
|---|---|---|
keys |
orders:write + tokens:write / tokens:read / tokens:write |
申请 / 查 / 废弃 API Key |
orders |
orders:create / orders:read / orders:refund |
下单 / 查 / 退款订单 |
请求 token 时只需要传父 scope(scope=keys 或 scope=orders),baozi 会自动展开为所有对应子 scope 的访问权限。
也可精细化请求子 scope(scope=tokens:read tokens:write),适用于需要最小权限的场景。
⚠️ 重要:M2M App 在你的 IDP 后台必须实际授予这些 scope,否则 IDP 会静默丢弃未配置的 scope,JWT 里的 scope 字段为空,调 baozi 会返 403 MISSING_SCOPE。
⚠️ 父 scope 不会跨域:keys 不会包含 orders:create,反之亦然。每个资源域的父 scope 独立。
1.2 创建 M2M Role + 授权
| UI 路径 | 操作 |
|---|---|
后台 Roles → + Create role → 命名 baozi Third-Party Keys |
创 Role |
| Role 配置页 → Machine-to-machine tab | 加上述 3 个 scope |
1.3 创建 M2M App + 授权 Role
| UI 路径 | 操作 |
|---|---|
| 后台 Applications → + Create application → Machine-to-machine | 创 M2M App |
| M2M App 配置页 → Roles tab | 勾 baozi Third-Party Keys Role |
M2M App 创建后不要勾 IDP 内置的「管理 API」相关 Resource,避免越权。
1.4 拿到 client_id + client_secret
M2M App 配置页 → Details → 复制 Client ID 和 Client Secret。仅展示一次,你保管,不要发给 baozi。
1.5 确认 end_user(无需绑定)
OAuth2 client_credentials 只验证 M2M App 本身的身份,不绑定任何 baozi 用户。API Key 归属于请求中 external_user_id 所指定的 end_user。
1.5.1 external_user_id 的语义
external_user_id 是 end_user 在 ohooho 统一认证体系中的稳定唯一标识,对应 ohooho 统一认证 user.id(UUID 格式)。
baozi 用 M2M token 调 GET https://login.ohoooho.com/api/users/{external_user_id} 反查 ohooho 统一认证,拿到完整 profile(含 sub / primaryEmail / primaryPhone / identities),再用 sub 在本地 users 表 upsert 拿到 newapi_uid。
| user id 类型 | baozi 处理方式 |
|---|---|
ohoooho 统一认证 user.id(UUID) |
✅ 三方传 external_user_id=<UUID>,baozi 用 M2M token 调 GET /api/users/{id} 反查 |
sub(OIDC subject claim) |
❌ ohooho 统一认证内部主键,三方系统不持有,不可作为 external_user_id |
email / phone / username |
❌ 不参与身份识别,不要传 |
| 第三方登录 ID(微信 openid / GitHub id / 企业微信 userid) | ❌ 三方系统自有,baozi 通过 identities API 反查,不要传 |
external_user_info(自由文本字段) |
⚪ 可选展示/追溯字段,不参与身份识别(详 §1.5.3) |
baozi users.id(响应中的 user_id 自增整数) |
⚠️ baozi 内部主键,仅供 baozi 内部关联使用 |
1.5.2 字段说明
external_user_id:end_user 在 ohooho 统一认证体系中的稳定唯一标识,对应user.id(UUID)。external_user_info:可选展示/追溯字段,不参与身份识别。任意字符串,max 512 字符(UTF-8)。baozi 透传到baozi_orders.external_user_info列,对账与 UI 显示使用。示例:"user-001"/"微信昵称"/"team-A 用户"/"备注"/"13800001111"。client_id:M2M App 的 OAuth2 客户端标识。OAuth2client_credentials是机器对机器认证,不与任何 baozi 用户绑定。
1.5.3 external_user_id 获取流程
- end_user 使用任意 OIDC connector(邮箱 / 手机 / 微信 / GitHub)在 https://baozi.ohoooho.com 完成一次登录。
- baozi 完成 OIDC 回调,通过 webhook 或
GET /api/v1/end_users?email=反查接口告知该 end_user 的user.id(UUID)。 - 三方系统记录该
user.id,用于后续下单。 - 三方下单时直接传
external_user_id=<UUID>,baozi 通过 M2M token 反查 ohooho 统一认证,不依赖 end_user 使用的具体 connector。
补充说明:
- 同一个 M2M App 可为不同
external_user_id创建不同 key(每个订单独立 key)。 - 同一个 end_user 可从多个 M2M App 各拿到独立 key(都在该用户的 baozi 账号下,登录 baozi 后台可见)。
1.5.4 admin checkout 例外
baozi 内部运营对账 / 补单时,可能传整数 baozi users.id 作为 external_user_id(admin checkout 流程)。该路径仅供 baozi 内部使用:服务端先按 sub 查询;若 external_user_id 为纯数字且按 sub 查不到,则 WHERE id = ? 查询 baozi users.id,并打 [partner/orders] admin checkout warn 日志。
对于使用本地 users.id 整数或 sub 字符串的旧集成,服务端按 ohooho 统一认证 user.id UUID 匹配失败后,会按上述 admin checkout 逻辑兜底查询。
2. 接入流程
2.1 拿 access token(OAuth2 client_credentials)
curl -X POST https://login.ohoooho.com/oidc/token \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "scope=orders:write tokens:write tokens:read"
返回:
{
"access_token": "***",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "orders:write tokens:write tokens:read"
}
access_token 默认 1 小时过期,建议在调用前 5 分钟刷新。
2.2 查 baozi 可购买套餐(推荐先调)
先用这个端点查可购买的套餐 + 价格 + tier 列表,再调 POST /api/v1/partner/orders(一步下单+创 key)创 key。
curl https://baozi.ohoooho.com/api/v1/packages \
-H "Authorization: Bearer ***"
需要 scope: orders:write + tokens:write
响应 200:
{
"success": true,
"count": 6,
"packages": [
{
"package_id": "pkg-trial-7day",
"name": "7 天体验套餐",
"price_yuan": 8,
"price_period": "7天",
"duration_days": 7,
"monthly_quota": 1000,
"tiers": [
{ "tier_id": "tier-trial-7day-std", "tier_name": "Standard", "monthly_quota": 1000, "price_yuan": 8 }
]
},
{
"package_id": "pkg-daily-chat",
"name": "日常 chat 套餐",
"price_yuan": 28,
"price_period": "月",
"duration_days": 30,
"monthly_quota": 5000,
"tiers": [
{ "tier_name": "Lite", "monthly_quota": 2000, "price_yuan": 18 },
{ "tier_name": "Standard", "monthly_quota": 5000, "price_yuan": 28 },
{ "tier_name": "Pro", "monthly_quota": 12500, "price_yuan": 78 }
]
}
]
}
字段说明:
| 字段 | 说明 |
|---|---|
package_id |
套餐 ID,传给 POST /api/v1/partner/orders 的 package_id |
price_period |
月 / 年 / 7天(决定 period 能传什么值) |
duration_days |
套餐有效天数(7天 = 7,月 = 30,年 = 365) |
tiers |
多档套餐有多个 tier;单档套餐 tiers 数组只有 1 个 |
monthly_quota |
配额量(token 数) |
用法:
- 单档套餐 → 直接用
package_id+period - 多档套餐 → 选一个 tier 后传
tier_id;不传则用recommended_order最小的(通常是 Lite)
2.3 申请 API Key(核心)
curl -X POST https://baozi.ohoooho.com/api/v1/tokens \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"package_id": "pkg-daily-chat",
"period": "month",
"external_order_id": "my-order-2026-08-15-001"
}'
每个字段详细参数使用和限制说明(K-312-12 完整化):
| 字段 | 类型 | 必填 | 长度限制 | 格式校验 | 示例值 | 含义 |
|---|---|---|---|---|---|---|
package_id |
string | ✅ | 1-64 chars | baozi 套餐 ID(来自 GET /api/v1/packages) |
"pkg-daily-chat" |
baozi 套餐标识 |
period |
string | ✅ | enum | month / year / 月 / 年 |
"month" |
计费周期 |
tier_id |
string | ⚪ 可选 | 1-64 chars | 多档套餐时指定 tier(来自 packages.tiers[].tier_id) |
"tier-trial-7day-std" |
多档套餐选 tier;不传则用 recommended_order 最小的(通常 Lite) |
external_order_id |
string | ⚪ 可选 | 1-128 chars | 唯一,三方自生成(推荐 UUID v4 或带日期前缀) | "my-order-2026-09-18-001" |
三方订单号 = 幂等键(baozi 去重用) |
external_user_id |
string | ⚪ 可选(§3.1 推荐必填) | 1-128 chars | UUID 推荐(ohoooho 统一认证 user.id 是 UUID 格式) |
"abc12345-1234-5678-9abc-def012345678" |
end_user 在 ohooho 统一认证体系中的稳定唯一标识;baozi 用 M2M token 调 GET /api/users/{id} 反查 |
external_user_info |
string | ⚪ 可选 | 0-512 chars | 无格式限制,"想传啥传啥" | "user@example.com" / "13800001111" / "wxid_abc" / "备注" |
三方系统里的 user 标识/备注,展示/追溯用,不参与身份识别;baozi 透传到订单表 |
settlement_mode |
string | ⚪ 可选 | enum | baozi_paid(你付 baozi) / external_paid(已收款,仅记账) |
"baozi_paid" |
结算模式(默认 baozi_paid) |
settlement_amount_yuan |
number | ⚠️ external_paid 时必填 |
0 < x ≤ 1000000 | 正数(元,2 位小数) | 199.00 |
已结算金额(external_paid 模式必填,baozi_paid 模式按套餐价) |
token_name |
string | ⚪ 可选 | 1-64 chars | 自由文本(new-api 限 50 chars,建议 ≤50) | "my-team-key" |
API Key 名称(默认 co-${pkg.id}-${ts}) |
metadata |
object | ⚪ 可选 | JSON < 4KB | baozi 透传(不解析) | {"campaign":"promo-2026"} |
三方自定义元数据,透传到订单 metadata 字段 |
HTTP Header:
| Header | 必填 | 长度限制 | 格式 | 含义 |
|---|---|---|---|---|
Authorization: Bearer <token> |
✅ | - | ohooho 统一认证 JWT(ES384) | M2M access_token |
Content-Type |
✅ | - | application/json |
- |
X-Idempotency-Key |
⚪ 可选 | 1-128 chars | 推荐 UUID v4 | 幂等键(与 external_order_id 二选一或同时带) |
字段名变更说明:
external_user_id:固定为 ohoooho 统一认证user.idUUID。baozi 用 M2M token 反查 ohooho 统一认证,不依赖 baozi users 表 sub 列。external_user_info:替代旧名end_user_identifier/customer_email/end_user_email。语义更开放,max 512 字符长度限制,不参与身份识别。/api/v1/tokens与external_user_id:/api/v1/tokens是 admin session 创 key 的轻量接口(不传 end_user);end_user 主路径为/api/v1/partner/orders。/api/v1/tokens仅供 admin 补单使用。
响应 200(订单 + API Key 一次给齐):
{
"success": true,
"order": {
"order_id": 87,
"package_id": "pkg-daily-chat",
"package_name": "日常 chat 套餐",
"period": "month",
"quota_added": 5000,
"amount_yuan": 28,
"status": "paid",
"transaction_id": "my-order-2026-08-15-001",
"created_at": 1723723456789
},
"api_key": {
"key_id": 188,
"baozi_key_id": 5,
"api_key": "***",
"quota_added": 5000,
"remaining_quota": 5000,
"status": 1,
"created_at": 1723723456789,
"usage_hint": "Use this key in Authorization: Bearer <api_key> header to call baozi models"
}
}
⚠️ 完整 api_key 仅在创建时返回一次,请立即安全保存。后续查询只能拿 masked key。
2.4 用 API Key 调 baozi 模型
curl -X POST https://llm.ohoooho.com/v1/chat/completions \
-H "Authorization: Bearer sk-baoz…xxxx" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": "Hello"}]
}'
3. API 接口
3.1 POST /api/v1/partner/orders — 申请 API Key(主路径)
每个字段详细参数使用和限制说明(K-312-12 完整化):
| 字段 | 类型 | 必填 | 长度限制 | 格式校验 | 示例值 | 含义 |
|---|---|---|---|---|---|---|
package_id |
string | ✅ | 1-64 chars | baozi 套餐 ID(来自 GET /api/v1/packages,必须可购买) |
"pkg-daily-chat" |
baozi 套餐标识 |
period |
string | ✅ | enum | month / year / 月 / 年 |
"month" |
计费周期 |
tier_id |
string | ⚪ 可选 | 1-64 chars | 多档套餐时指定 tier(来自 packages.tiers[].tier_id) |
"tier-trial-7day-std" |
多档套餐选 tier;不传则用 recommended_order 最小的 |
external_order_id |
string | ⚪ 可选(强烈推荐) | 1-128 chars | 唯一,三方自生成(推荐 UUID v4) | "my-order-2026-09-18-001" |
三方订单号 = 幂等键 |
external_user_id |
string | ✅ | 1-128 chars | UUID 推荐(ohoooho 统一认证 user.id 是 UUID) |
"abc12345-1234-5678-9abc-def012345678" |
end_user 在 ohooho 统一认证体系中的稳定唯一标识;baozi 用 M2M token 调 GET /api/users/{id} 反查 |
external_user_info |
string | ⚪ 可选 | 0-512 chars | 无格式限制,"想传啥传啥" | "user@example.com" / "13800001111" / "wxid_abc" / "备注" |
三方系统里的 user 标识/备注,展示/追溯用,不参与身份识别 |
settlement_mode |
string | ⚪ 可选(默认 baozi_paid) |
enum | baozi_paid / external_paid |
"baozi_paid" |
结算模式 |
settlement_amount_yuan |
number | ⚠️ external_paid 时必填 |
0 < x ≤ 1000000 | 正数(元) | 199.00 |
已结算金额 |
token_name |
string | ⚪ 可选 | 1-64 chars | 自由文本(new-api 限 50 chars) | "my-team-key" |
API Key 名称 |
metadata |
object | ⚪ 可选 | JSON < 4KB | baozi 透传 | {"campaign":"promo-2026"} |
三方自定义元数据 |
HTTP Header:
| Header | 必填 | 长度限制 | 格式 | 含义 |
|---|---|---|---|---|
Authorization: Bearer <token> |
✅ | - | ohooho 统一认证 JWT(ES384) | M2M access_token |
Content-Type |
✅ | - | application/json |
- |
X-Idempotency-Key |
⚪ 可选 | 1-128 chars | 推荐 UUID v4 | 幂等键(与 external_order_id 二选一或同时带) |
响应 200:见 §2.3。
错误码:
| code | 含义 |
|---|---|
MISSING_PACKAGE_ID |
缺 package_id |
INVALID_PERIOD |
period 不是 month / year |
PACKAGE_NOT_FOUND |
套餐不存在或已下架 |
PACKAGE_TIER_NOT_FOUND |
套餐没有可用的 tier |
CLIENT_NOT_BOUND |
M2M App 未绑定 baozi 用户 |
AUDIENCE_MISMATCH |
token 与 baozi API 不匹配 |
SCOPE_MISSING |
token 缺 orders:write + tokens:write |
3.2 GET /api/v1/tokens — 查名下所有 keys
响应 200:
{
"success": true,
"count": 2,
"data": [
{
"baozi_key_id": 5,
"key_id": 188,
"key_name": "pkg-daily-chat-month",
"plan_name": "日常 chat 套餐",
"order_id": 87,
"masked_key": "sk-bao***fokI",
"status": 1,
"remain_quota": 9500,
"used_quota": 500,
"unlimited_quota": false,
"model_limits": null,
"expired_time": -1,
"created_at": 1723723456789,
"last_accessed": 1723723500000
}
]
}
3.3 GET /api/v1/tokens/:key_id — 单查
响应 200:
{
"success": true,
"data": {
"baozi_key_id": 5,
"key_id": 188,
"key_name": "pkg-daily-chat-month",
"plan_name": "日常 chat 套餐",
"order_id": 87,
"masked_key": "sk-bao***fokI",
"status": 1,
"remain_quota": 9500,
"used_quota": 500,
"unlimited_quota": false,
"model_limits": null,
"allow_ips": null,
"expired_time": -1,
"created_at": 1723723456789,
"last_accessed": 1723723500000
}
}
3.4 POST /api/v1/partner/orders/:key_id/topup — 充值加额度
背景:套餐决定初始 quota;充值在已有 key 上加额外 quota。
请求体:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
quota_delta |
✅ | integer | 充值配额(1 ~ 1e9) |
响应 200:
{
"success": true,
"key_id": 188,
"quota_delta": 50000,
"pre_quota": 9500,
"post_quota": 59500,
"topup_at": 1723723600000
}
3.5 DELETE /api/v1/tokens/:key_id — 废弃 key
响应 200:
{
"success": true,
"key_id": 188,
"status": "disabled",
"disabled_at": 1723723600000
}
废弃后 API Key 立即失效,baozi 保留记录用于审计。
3.6 POST /api/v1/partner/orders/:key_id/reset-secret — 重置密钥(泄露后用)
背景:泄露后立刻重置。baozi 用「删旧 + 建新」实现,保留原 plan_name + quota。不创建新订单,仅换 token。
响应 200:
{
"success": true,
"old_key_id": 188,
"new_key_id": 192,
"api_key": "***",
"remain_quota": 9500,
"reset_at": 1723723600000,
"warning": "Old key is now disabled. Save the new api_key now — it won't be shown again."
}
3.7 GET /api/v1/partner/orders (partner 视角)/:order_id — 查订单(对账用)
响应 200:
{
"success": true,
"order": {
"order_id": 87,
"package_id": "pkg-daily-chat",
"package_name": "日常 chat 套餐",
"period": "month",
"amount_cny": 28,
"quota_added": 5000,
"settlement_status": "settled",
"settled_at": 1723723456789,
"settled_amount_cny": null,
"external_order_id": "my-order-2026-08-15-001",
"end_user": {
"baozi_user_id": 5,
"username": "bz_a1b2c3d4e5",
"newapi_uid": 17
},
"status": "paid",
"created_at": 1723723456789
},
"api_keys": [
{
"baozi_key_id": 5,
"key_id": 188,
"key_name": "pkg-daily-chat-month",
"masked_key": "sk-bao***fokI",
"status": 1,
"remain_quota": 9500,
"used_quota": 500,
"created_at": 1723723456789
}
]
}
对账要点:
| 字段 | 对账逻辑 |
|---|---|
order.amount_cny |
你实际应付 baozi 多少钱 |
order.quota_added |
baozi 实际给你多少配额 |
order.settlement_status |
settled = 已对账;pending_settlement = 待 baozi 财务对账 |
api_keys[].remain_quota |
实时剩余配额(用户消耗后会减少) |
api_keys[].used_quota |
累计已用配额 |
3.8 POST /api/v1/partner/orders/:order_id/refund — 退款
退款走这个端点。已消费的 quota 不可追回,按剩余比例退。
请求体:
| 字段 | 必填 | 类型 | 说明 |
|---|---|---|---|
reason |
可选 | string | 退款原因(baozi 记录用) |
响应 200:
{
"success": true,
"order_id": 87,
"refunded_quota": 4500,
"refunded_amount_yuan": 25.2,
"remaining_api_keys_disabled": true,
"refunded_at": 1723723600000
}
3.9 GET /api/v1/partner/orders (partner 视角) — 列名下所有订单
查询参数:
| 参数 | 必填 | 类型 | 说明 |
|---|---|---|---|
limit |
可选 | int | 默认 50,最大 200 |
offset |
可选 | int | 默认 0 |
since |
可选 | int (timestamp ms) | 只返该时间之后的订单 |
响应 200:
{
"success": true,
"count": 2,
"orders": [
{
"order_id": 87,
"package_id": "pkg-daily-chat",
"amount_yuan": 28,
"quota_added": 5000,
"status": "paid",
"created_at": 1723723456789
}
]
}
3.10 POST /api/v1/partner/orders — 直接下单(创 key 同 §3.1)
跟 POST /api/v1/partner/orders 等价,专门用于不创 key 的纯下单场景(如充值包 / 月度结算)。
请求体:同 §3.1,但 package_id 可为纯 quota 包。
响应 200:
{
"success": true,
"order": {
"order_id": 88,
"package_id": "pkg-daily-chat",
"amount_yuan": 28,
"quota_added": 5000,
"status": "paid",
"created_at": 1723723456789
}
}
4. 数据契约(两套 schema 说明)
baozi 提供两类 API,字段命名风格不同,这是设计而非 bug:
| API 路径 | 字段风格 | 用途 | 例子 |
|---|---|---|---|
/api/packages(baozi 前端) |
camelCase | 人类浏览 baozi Web UI | { "id": "pkg-daily-chat", "priceYuan": 28, "monthlyQuota": 5000 } |
/api/v1/*(baozi-service) |
snake_case | 机器调用(OAuth2 / REST 业界惯例) | { "package_id": "pkg-daily-chat", "price_yuan": 28, "monthly_quota": 5000 } |
为什么分开:baozi 前端 API 跟前端展示一致(TypeScript 友好);第三方 API 跟 OAuth2 / RFC 6749 业界惯例一致(多语言 SDK 友好)。
集成方注意:调 baozi 第三方 API 请用 snake_case,不要混用。
5. 业务约束
| 约束 | 规则 |
|---|---|
| 套餐校验 | package_id 必须存在 + 可购买 |
| 周期校验 | period 必须 month / year |
| 年付折扣 | amount = 月价 × 12 × 0.833(年付约 8.3 折) |
| API Key 命名 | <package_id>-<period>-<order_id> |
| 展示策略 | 完整 key 仅在创建时返回一次;其他查询返 masked(***...XXXX) |
| 配额模型 | 套餐 + 充值 → 余额 → 消费 → 余额减少 |
| 过期时间 | API Key 默认永不过期;quota 有效期跟套餐周期(30 / 365 天) |
| 越权防护 | M2M App 只能管自己名下 orders + keys |
6. 错误码总表
| code | 含义 | 触发场景 |
|---|---|---|
MISSING_BEARER |
缺 Bearer token | Authorization 头缺失 |
INVALID_TOKEN |
token 解析失败 | token 格式错 / 签名错 |
TOKEN_EXPIRED |
token 过期 | access_token 超 1 小时 |
AUDIENCE_MISMATCH |
token 与 baozi API 不匹配 | token 是给其他 API 用的 |
SCOPE_MISSING |
token 缺 scope | 调顶层但 token scope 不够 |
MISSING_PACKAGE_ID |
缺 package_id |
必填 |
INVALID_PERIOD |
period 不是 month/year |
必填 |
PACKAGE_NOT_FOUND |
套餐不存在 / 已下架 | 套餐 ID 错 |
PACKAGE_TIER_NOT_FOUND |
套餐没有可用 tier | 套餐配置异常 |
INVALID_QUOTA_DELTA |
quota_delta 错 | topup 端点 |
INVALID_KEY_ID |
key_id 非整数 | 路径参数格式错 |
KEY_NOT_FOUND |
key 不存在 | key_id 错或不属于该 M2M App |
ORDER_NOT_FOUND |
order 不存在 | order_id 错或不属于该 M2M App |
CLIENT_NOT_BOUND |
M2M App 未绑定 baozi 用户 | 联系 baozi 运营 |
7. 沙箱 / 测试
目前 baozi 只有 生产环境(https://baozi.ohoooho.com/api/v1/*)。沙箱环境待开放。
测试技巧:
- 用
quota_delta = 1000试调 topup 端点(小额试玩) - 创建 key 后立即用
GET /api/v1/tokens/:key_id验证 key_id 正确 - 用
GET /orders/:order_id验证订单 + api_keys 双查
7. 集成要素
7.1 幂等性(Idempotency)
POST /api/v1/partner/orders 支持幂等请求,避免网络重试导致重复下单。
用法:在请求头加 X-Idempotency-Key,值为你自己生成的唯一字符串(建议 UUID)。
curl -X POST https://baozi.ohoooho.com/api/v1/tokens \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{ "package_id": "pkg-daily-chat", "period": "month", "external_order_id": "my-order-001" }'
规则:
- 同一
X-Idempotency-Key+ 同一 M2M App,30 秒内重复请求 → 返回原始订单(不创建新订单 / 不发新 key) - 超过 30 秒 → 视为新请求,正常处理
- key 必须你自己生成;不要用订单 ID 或时间戳(确保唯一性)
7.2 Webhook(订单状态变更通知)
baozi 支持 webhook 推送订单状态变更(paid / refunded / disabled)。
配置:联系 baozi 运营(support@ohoooho.com),提供你的回调 URL,baozi 会在你后台登记。
签名校验:baozi 用 HMAC-SHA256 签名 webhook body,请求头 X-Baozi-Signature 是签名值,你用 baozi 运营给你的 webhook secret 校验。
重试:失败会重试 3 次(指数退避)。可在 GET /api/v1/partner/orders (partner 视角)/:order_id 的 webhook_deliveries 字段看历史投递记录。
7.3 Refresh Token
baozi OAuth2 走 client_credentials grant(RFC 6749 §4.4),不发 refresh token —— 这是 OAuth2 标准行为,不是 bug。
用法:access_token 过期(默认 1 小时)后,重新调 POST /oidc/token 拿新 access_token。建议在过期前 5 分钟刷新。
7.4 CORS
baozi 第三方 API 支持浏览器直接跨域调用,开放所有第三方域名(2026-08-27 起)。
用法:集成方可以直接从浏览器(Web 前端 / H5)调 baozi API,浏览器会自动处理 CORS 预检(preflight OPTIONS)和响应头。
支持的方法:GET / POST / PUT / DELETE / PATCH / OPTIONS
支持的请求头:Authorization / Content-Type / X-Idempotency-Key / X-Baozi-Signature
安全边界:
- baozi 第三方 API 用 Bearer Token 鉴权(M2M access_token),不是 cookie-based
- 浏览器跨域不会自动带 cookie,所以 csrf 风险不存在
- 集成方前端必须自己保管 token(推荐 localStorage / sessionStorage,避免 XSS)
- preflight 缓存 24 小时(
Access-Control-Max-Age: 86400),减少 OPTIONS 请求次数
7.5 M2M App 停用
如需停止某个 M2M App 的 baozi 访问权限(员工离职 / 安全事件):
| 操作 | 效果 |
|---|---|
禁用(admin 设 enabled=0) |
该 M2M App 后续所有请求返 403 CLIENT_DISABLED |
| 彻底注销 | 在你自己 ohooho 统一认证控制台后台删除 M2M App(baozi 不需介入) |
⚠️ 禁用不会自动撤销已签发的 API Key。如需撤销历史 key,由你主动调 DELETE /api/v1/tokens/:key_id。
8. 联系 baozi
- 邮箱:support@ohoooho.com(仅当 ohooho 统一认证 / baozi 服务故障或合规需求时联系;流程问题请先翻文档)
- client_secret 泄露 / 撤销:去你自己的 ohooho 统一认证控制台后台处理(baozi 不参与 secret 生命周期)
- API 故障 / 反馈:写邮件给 support@ohoooho.com,附
key_id和request_ip - 退款:走
POST /api/v1/partner/orders/:order_id/refund(已消费的 quota 不可追回)