第三方 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 时只需要传父 scopescope=keysscope=orders),baozi 会自动展开为所有对应子 scope 的访问权限。

也可精细化请求子 scopescope=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 applicationMachine-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 IDClient 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 客户端标识。OAuth2 client_credentials 是机器对机器认证,不与任何 baozi 用户绑定。

1.5.3 external_user_id 获取流程

  1. end_user 使用任意 OIDC connector(邮箱 / 手机 / 微信 / GitHub)在 https://baozi.ohoooho.com 完成一次登录。
  2. baozi 完成 OIDC 回调,通过 webhook 或 GET /api/v1/end_users?email= 反查接口告知该 end_user 的 user.id(UUID)。
  3. 三方系统记录该 user.id,用于后续下单。
  4. 三方下单时直接传 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/orderspackage_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) &quot;my-team-key&quot; API Key 名称(默认 co-${pkg.id}-${ts}
metadata object ⚪ 可选 JSON < 4KB baozi 透传(不解析) {&quot;campaign&quot;:&quot;promo-2026&quot;} 三方自定义元数据,透传到订单 metadata 字段

HTTP Header

Header 必填 长度限制 格式 含义
Authorization: Bearer &lt;token&gt; - 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.id UUID。baozi 用 M2M token 反查 ohooho 统一认证,不依赖 baozi users 表 sub 列
  • external_user_info:替代旧名 end_user_identifier / customer_email / end_user_email。语义更开放,max 512 字符长度限制,不参与身份识别
  • /api/v1/tokensexternal_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,必须可购买) &quot;pkg-daily-chat&quot; baozi 套餐标识
period string enum month / year / / &quot;month&quot; 计费周期
tier_id string ⚪ 可选 1-64 chars 多档套餐时指定 tier(来自 packages.tiers[].tier_id &quot;tier-trial-7day-std&quot; 多档套餐选 tier;不传则用 recommended_order 最小的
external_order_id string ⚪ 可选(强烈推荐) 1-128 chars 唯一,三方自生成(推荐 UUID v4) &quot;my-order-2026-09-18-001&quot; 三方订单号 = 幂等键
external_user_id string 1-128 chars UUID 推荐(ohoooho 统一认证 user.id 是 UUID) &quot;abc12345-1234-5678-9abc-def012345678&quot; end_user 在 ohooho 统一认证体系中的稳定唯一标识;baozi 用 M2M token 调 GET /api/users/{id} 反查
external_user_info string ⚪ 可选 0-512 chars 无格式限制,"想传啥传啥" &quot;user@example.com&quot; / &quot;13800001111&quot; / &quot;wxid_abc&quot; / &quot;备注&quot; 三方系统里的 user 标识/备注,展示/追溯用,不参与身份识别
settlement_mode string ⚪ 可选(默认 baozi_paid enum baozi_paid / external_paid &quot;baozi_paid&quot; 结算模式
settlement_amount_yuan number ⚠️ external_paid 时必填 0 < x ≤ 1000000 正数(元) 199.00 已结算金额
token_name string ⚪ 可选 1-64 chars 自由文本(new-api 限 50 chars) &quot;my-team-key&quot; API Key 名称
metadata object ⚪ 可选 JSON < 4KB baozi 透传 {&quot;campaign&quot;:&quot;promo-2026&quot;} 三方自定义元数据

HTTP Header

Header 必填 长度限制 格式 含义
Authorization: Bearer &lt;token&gt; - 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 { &quot;id&quot;: &quot;pkg-daily-chat&quot;, &quot;priceYuan&quot;: 28, &quot;monthlyQuota&quot;: 5000 }
/api/v1/*(baozi-service) snake_case 机器调用(OAuth2 / REST 业界惯例) { &quot;package_id&quot;: &quot;pkg-daily-chat&quot;, &quot;price_yuan&quot;: 28, &quot;monthly_quota&quot;: 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 命名 &lt;package_id&gt;-&lt;period&gt;-&lt;order_id&gt;
展示策略 完整 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_idwebhook_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_idrequest_ip
  • 退款:走 POST /api/v1/partner/orders/:order_id/refund(已消费的 quota 不可追回)

文档问题反馈:飞书群 @ 闲云 · 第三方 API Key 集成文档(内部)