# IP 中台完整机器可读开发文档 本文档为 LLM、代码代理和第三方开发者提供单文件上下文。生成源包括公开开发者指南、OpenAPI 3.1 契约和仓库中的公开示例。实时模型列表不固化在本文,必须读取动态索引。 ## 1. 文档优先级与实现约束 遇到信息冲突时,按以下顺序判断: 1. `/openapi.json`:HTTP 路径、方法、参数、请求与响应字段、鉴权方式。 2. `/v1/platform/models/documentation` 及其逐模型链接:当前可用模型、`model_key`、分类、介绍和逐模型调用体。 3. `/developer-integration-guide.md`:业务流程、安全边界、幂等、回跳、Webhook 与错误处理。 4. 示例源码:用于理解实现方式;若与 OpenAPI 不一致,必须以 OpenAPI 为准并停止猜测。 禁止通过字段名称推断未公开能力,禁止自造列表、查询、支付或用户接口。第三方应用只能处理当前完成授权的用户;不得全量拉取、搜索、导出或跨应用关联会员。 ## 2. 接入决策 | 目标 | 身份模式 | 首个入口 | 后续调用 | |---|---|---|---| | 应用服务端使用开发者本人额度调用模型 | Developer API Key | 开发者控制台创建并确认交付 Key | `POST /v1/developer/models/{modelKey}/invoke`,请求头 `X-API-Key` | | 让用户登录,并读取该用户资料、会员或积分 | OAuth 2.1 Authorization Code + PKCE | 浏览器跳转 `/oauth/authorize` | 服务端换 Token,再按 Scope 调用用户接口 | | 代表当前用户调用模型并消费其积分 | OAuth Access Token + `points.spend` | 同上 | `POST /v1/models/{modelId}/invoke` | | 为当前用户创建积分充值或会员升级入口 | OAuth Access Token + `billing.initiate` | `POST /v1/user/billing-entries` | 浏览器跳转 `billing_url`,回跳后查询单个入口状态 | ## 3. LLM 实现检查表 1. 从部署配置读取平台 Origin;生产环境只使用 HTTPS。 2. 选择上表唯一匹配的身份模式,不混用 Cookie、OAuth Token、API Key 或 Client Secret。 3. OAuth 使用 PKCE S256,校验 `state` 与 `nonce`,并在服务端兑换授权码。 4. 使用 `issuer + sub` 作为应用内外部身份键;不要使用手机号、邮箱或平台内部 ID 关联用户。 5. 从实时模型索引读取 `model_key`;不要使用显示标题或供应商 Model Name 拼接调用 URL。 6. 每个新模型业务请求生成唯一 `idempotency_key`;同一业务重试必须复用同一个键和完全相同的请求体。 7. 对 HTTP 状态、业务错误码、`provider_unknown` 和支付查询状态分别处理;不能把浏览器回跳参数当作支付成功凭据。 8. Webhook 验签并按 `event_id` 去重,但最终支付与会员状态仍由对应查询接口确认。 9. 日志和错误上报中删除 API Key、Secret、Token、授权码、完整个人资料和敏感模型输入。 10. 上线前按 OpenAPI 校验请求与响应,并重新读取实时模型文档完成一次端到端联调。 ## 4. 实时模型文档 - Markdown 索引:http://127.0.0.1:5175/v1/platform/models/documentation - JSON 索引:http://127.0.0.1:5175/v1/platform/models - 默认价格:http://127.0.0.1:5175/v1/platform/models/pricing - 单模型 Markdown:http://127.0.0.1:5175/v1/platform/models/{model_key}/documentation 逐模型文档包含 cURL、Node.js 和 Python 示例。模型由后台动态维护,本文不复制模型清单,以免上下架或字段修改后形成过期快照。 ## 5. 开发者安全接入指南 # IP 中台开发者安全接入指南 ## 面向 LLM 与代码代理的读取入口 实现对接前按以下顺序读取,避免从可视化页面、过期模型快照或字段名称猜测接口: 1. `/llms.txt`:机器文档导航、真源顺序与安全边界。 2. `/llms-full.txt`:本指南、完整 OpenAPI 和全部示例源码合并后的单文件上下文。 3. `/openapi.json`:路径、方法、请求/响应字段与鉴权方式的唯一机器可读契约。 4. `/v1/platform/models/documentation`:从后台当前配置实时生成的模型 Markdown 索引。 5. `/v1/platform/models/{model_key}/documentation`:逐模型调用体及 cURL、Node.js、Python 示例。 字段或示例出现冲突时以 OpenAPI 为准;模型可用性、`model_key`、分类与介绍以实时模型 Markdown 为准。不要自造接口,也不要保存永久模型白名单。 ## 1. Base URL 生产环境使用平台部署域名,所有业务接口位于 `/v1`: ```text https://<平台域名>/v1 ``` 本文示例使用 `https://idp.example.com` 作为占位域名。生产请求必须使用 HTTPS,不能把 API Key、Client Secret、Access Token 放在 URL 查询参数中。 ### 1.1 在开发者控制台配置应用 进入开发者控制台的“应用管理”,应用以表格展示;点击某一行的“详情”后,弹窗只展示该应用。点击右下角“修改”可在原位置编辑允许变更的字段。应用配置与接口字段的对应关系如下: | 控制台字段 | API 字段 | 是否可修改 | 接入用途与规则 | |---|---|---|---| | 名称 | `name` | 是 | 授权页展示给用户的应用名称,建议与实际产品名称一致。 | | 应用首页/介绍页 | `homepage_url` | 是 | AI 超市“打开应用”的公开跳转地址。仅允许无账号密码信息的完整 HTTP(S) 地址;建议生产使用 HTTPS。 | | Client ID | `client_id` | 否 | 应用的公开唯一标识,用于 OAuth 授权请求、Token 兑换和按应用隔离用户 `sub`;可以出现在浏览器中。 | | 创建时间 | `created_at` | 否 | Client 创建时间,ISO 8601 格式。 | | 最低会员等级 | `min_member_level` | 是 | OAuth 授权时实时校验当前用户等级;低于门槛时拒绝授权。L0-L4 为平台统一数值空间。 | | 授权范围 | `allowed_scopes` | 是 | 该 Client 最多允许申请的 Scope。实际授权请求的 `scope` 必须是其子集。 | | 回调白名单 | `redirect_uris` | 是 | OAuth 授权码返回地址。必须登记完整 URI,并与授权请求中的 `redirect_uri` 精确匹配。 | | 支付回跳白名单 | `billing_return_uris` | 是 | 会员购买或积分充值完成后的应用回跳地址;创建支付入口时的 `return_uri` 必须精确匹配。 | | 事件 Webhook | 独立 Webhook 配置 | 是 | 每个 Client 一个 HTTPS 服务端接收地址,用于接收支付、会员和积分事件。不能填写前端页面地址。 | | Secret | `client_secret_masked` | 仅轮换 | 列表和详情只返回固定掩码 `****************` 表示已经签发,不返回明文、哈希或尾号;明文仅在首次领取或轮换响应中出现一次。 | 授权范围在控制台使用中文显示,协议请求仍使用右侧固定值: | 控制台名称 | Scope | 何时申请 | |---|---|---| | OIDC 登录身份 | `openid` | 需要标准 OIDC 登录和 `id_token` 时。 | | 基础身份 | `identity` | 需要调用 `/v1/oauth/userinfo` 获取应用专属 `sub` 和脱敏资料时。 | | 会员等级 | `membership.read` | 需要读取当前授权用户实时会员等级或套餐详情时。 | | 积分余额 | `points.read` | 需要读取当前授权用户钱包摘要时。 | | 积分消费 | `points.spend` | 需要以当前授权用户积分调用模型时。 | | 会员与充值入口 | `billing.initiate` | 需要创建当前用户的一次性会员购买或积分充值入口时。 | 首次创建应用时直接提交 Sandbox 配置。`request_id` 必须是本次创建意图唯一的 UUID;请求超时、断连或响应丢失后的重试必须复用同一 UUID 和完全相同的配置: ```http POST /v1/developer/sandboxes Cookie: <平台首方会话> Content-Type: application/json { "request_id": "6eec6a0e-3af5-4f32-9a25-ff84a50468e9", "configuration": { "name": "示例应用", "homepage_url": "https://app.example.com", "redirect_uris": ["https://app.example.com/oauth/callback"], "billing_return_uris": ["https://app.example.com/payment-complete"], "allowed_scopes": ["openid", "identity", "membership.read", "points.read", "points.spend", "billing.initiate"], "min_member_level": 1 } } ``` 创建后在应用详情读取当前有效测试凭据,并成功完成至少一次 Sandbox OAuth 兑换或 Mock 模型调用。当前凭据在 Sandbox 为 Active 时可重复读取;暂停会隐藏并禁用但保留加密副本,恢复成功后重新显示,重生成会使旧 Secret、测试密码和 Token 失效。凭据不得写入浏览器持久存储、正式库或日志。状态返回 `basic_integration_completed=true` 后,才能调用 `POST /v1/developer/sandboxes/{sandboxClientId}/production-applications` 提交 `{request_id}` 申请正式上线。该操作只创建 `pending` Client,仍需 Admin 审核;任何测试凭据、用户、Webhook、模型、支付和日志都不会迁移。直接调用 `POST /v1/developer/clients` 固定返回 `409 sandbox_required`。 修改应用使用 `PATCH /v1/developer/clients/{clientId}`,请求体至少包含一个可修改字段。名称和最低会员等级直接生效;`homepage_url` 可传完整地址,显式传 `null` 表示清空。已启用应用若修改首页地址、授权范围、OAuth 回调白名单或支付回跳白名单,将立即从 AI 超市下架,撤销该 Client 的 Refresh Token、清除旧 Client Secret,并转为 `pending` 重新审核。审核通过后应用自动重新上架,开发者必须领取并部署新 Secret,用户也需要重新授权。Sandbox 来源的正式申请被拒后,不得用 PATCH 改写旧 rejected Client;应先在原 Sandbox 配置中调整,再重新申请正式上线,系统会创建新的 pending Client 并保留旧拒审记录。 应用状态含义:`pending` 为等待审核,`active` 为可授权和调用,`disabled` 为平台停用,`rejected` 为审核拒绝。`active` 应用可以领取审核签发的一次性 Client Secret;`active` 或 `disabled` 应用可以主动轮换。首次领取使用 `GET /v1/developer/clients/{clientId}/secret`;主动轮换使用 `POST /v1/developer/clients/{clientId}/rotate-secret`。轮换成功后旧 Secret 立即失效,应先准备安全的部署切换流程,收到新明文后存入服务端秘密管理系统,禁止放入浏览器、移动端包、源码或日志。 AI 超市读取公开接口 `GET /v1/platform/apps?page=1&page_size=20`。平台只列出当前 `active` 的应用;`pending`、`rejected`、`disabled` 不会出现。公开项固定为 `client_id`、`name`、`homepage_url`、`min_member_level`、`created_at`,不会泄露开发者资料、回调地址、支付回跳地址、授权范围、Secret 或会员数据。历史应用尚未配置 `homepage_url` 时仍可展示,但入口保持不可点击;补充地址并重新审核后即可跳转。 上述 Client 管理和 Webhook 配置端点依赖平台首方 Session,只供登录后的开发者控制台调用。第三方应用运行时不应调用这些端点;运行时只使用已配置的 `client_id`、服务端安全保存的 Secret,以及对应的 OAuth/API Key 业务端点。 上线前逐项确认:授权请求只申请已登记 Scope;开发、测试、生产回调地址分别完整登记;支付回跳不携带可信归因参数;应用后端校验 OAuth `state` 和 `nonce`;Token 兑换只在服务端完成;Webhook 使用原始请求体验签并按 `event_id` 去重;不得尝试拉取、搜索或关联其他会员。 ## Sandbox 联调 新应用默认在控制台创建 Sandbox,随后在应用详情持续显示 `sbx_` Client ID、当前有效 Client Secret 和虚构测试账号,直至凭据失效或重新生成。测试用户通过 `POST /v1/auth/login` 传入 `sandbox_client_id` 登录;服务端只写入独立的 `idp_sbx_session`,不会读取正式 Cookie 或正式用户。 Sandbox 继续使用 OAuth Authorization Code + PKCE,但 code、refresh token、issuer、签名 key、JWKS、Cookie、Redis 和数据库均独立。模型调用只接受虚构测试账号换取的 OAuth Access Token,并只扣该测试账号的 Sandbox 测试积分;Sandbox 不签发 API Key。历史 `idp_sbx_` Key 固定返回 `401 invalid_api_key`,不会查库或回退正式环境。只可调用五个 Mock 模型:`sbx-mock-success`、`sbx-mock-failure`、`sbx-mock-provider-unknown`、`sbx-mock-content-blocked`、`sbx-mock-timeout`;它们不会访问模型供应商。测试凭据、Webhook、日志、测试数据和支付状态绝不迁移到正式 Client。 Sandbox 复用现有积分预扣/确认/释放、OAuth 模型网关、充值和内容投诉契约,但所有记录只写入 Sandbox 数据库。充值响应的 `payment_url` 使用 `mock-pay:` 测试 URL,其中包含该订单的测试回调字段与单次签名;订单号固定为 `sbx_pay_` 前缀,回调入口命中该前缀后只查询 Sandbox,绝不调用微信、支付宝或正式商户。Mock 模型不会访问供应商,也不会把输入输出写入用量或审计。Sandbox Webhook 仍执行本文的 HTTPS、DNS 绑定、HMAC、300 秒时间窗、`event_id` 去重和密钥轮换规则,并限制为每 Client 每分钟 20 个测试事件、最多 100 个待投递事件。 ## 2. 使用开发者 API Key 调用模型 适用于开发者自己的服务端直接调用,不代表任何终端用户身份。 1. 登录开发者中心,选择本人一个已审核通过的 Active Client 创建密钥。明文只展示一次。 2. 保存明文后确认交付,Key 才会从 `delivery_pending` 激活。 3. 从文档中心的动态[模型调用文档索引](/docs/models)或机器可读的 `/v1/platform/models/documentation` 取得 `model_key`,再进入对应模型的独立调用页面。 4. 使用唯一 `X-API-Key` 请求头调用模型。 文档索引由公开只读接口 `GET /v1/platform/models` 动态生成。该接口只返回当前可用模型的文档字段,不返回供应商、成本、应用定价、会员门槛或任何凭证。字段不要混用: | 字段 | 用途 | |---|---| | `model_key` | 平台稳定调用标识,写入 `/v1/developer/models/{modelKey}/invoke`;同一模型改展示标题或供应商 Model Name 时不变。 | | `display_name` | 文档索引展示标题,仅用于界面。 | | `model_name` | 平台发送给供应商的实际 Model Name,供接入核对,不作为调用 URL。 | | `model_description` | 后台维护的模型能力介绍,可为空。 | | `model_categories` | 多能力分类数组,可同时包含对话、生图、视频、语音、嵌入、重排序等。 | | `model_category` | `model_categories` 首项,仅为旧客户端兼容;新接入应读取数组。 | 当前默认价格位于[模型价格](/docs/models/pricing),机器可读接口为 `GET /v1/platform/models/pricing`。响应中的 `points_to_cny_rate` 表示 1 积分对应的人民币分值,`default_points_per_call` 表示平台默认单次调用积分价;指定 Client 如配置独立价格,以实际调用扣减为准。公开价格接口不会返回供应商成本、供应商身份或 Client 覆盖价格。 默认按次价格的统一输入包络为:`input` JSON 序列化后最多 32,000 个 UTF-8 字节;超限返回 HTTP 413 `content_too_large`,且不会预扣积分或调用供应商。文本/代码模型的生成上限由后台当前价格版本固定为 8,000 个输出单位。 ```http POST /v1/developer/models/{modelKey}/invoke X-API-Key: Content-Type: application/json { "idempotency_key": "order-20260819-001", "input": { "messages": [{ "role": "user", "content": "请回复 OK" }] } } ``` - Client 由 Key 的唯一绑定确定;`client_id` 可省略。兼容请求若携带该字段,只用于核对是否与绑定值一致,不能切换 Client。 - API Key 调用使用 API Key 所属开发者本人的积分钱包。 - API Key 只能用于 `/v1/developer/models/*`,不能用于用户登录、会员查询或 OAuth 接口。 - API Key 只保存在服务端秘密管理系统,不得进入源码、浏览器、日志或错误上报。 `doubao-embedding-vision` 使用 OpenAI 兼容的 Embeddings 协议。平台统一调用地址不变,待向量化文本放在外层 `input` 对象的内层 `input` 数组中: ```http POST /v1/developer/models/doubao-embedding-vision/invoke X-API-Key: Content-Type: application/json { "idempotency_key": "embedding-20260822-001", "input": { "input": ["用户希望找回项目中的支付回调设计"], "encoding_format": "float" } } ``` 成功响应的 `output.data[].embedding` 为稠密向量,`output.data[].index` 对应原输入数组顺序。知识库内容和查询文本必须使用同一模型与相同向量维度;入库时保存原文 ID 与向量的对应关系,查询时使用余弦相似度或向量数据库取得 Top K。网络重试必须复用原幂等键和原请求体。不要把聊天模型的 `messages` 请求体直接用于该模型;完整示例见文档中心的对应模型页面。 ## 3. 接入平台用户登录 第三方应用必须使用 OAuth2 Authorization Code + PKCE,不得收集、代理或保存用户的平台密码。 接入顺序: 1. 在开发者中心创建 Sandbox 应用,登记精确回调地址、允许 Scope 和最低会员等级,并完成一次 OAuth 兑换或 Mock 模型调用。 2. 提交正式申请并等待管理员审核;通过后由 Client 所有者领取一次性 Client Secret。 3. 生成 `state`、`nonce`、`code_verifier` 和 S256 `code_challenge`。 4. 把用户跳转到 IdP Web 的 `GET /oauth/authorize`,由平台完成登录、展示当前隐私政策、记录同意和授权。第三方应用不得自行收集平台隐私同意,也不得调用同意记录接口。 5. 回调后由应用服务端调用 `POST /v1/oauth/token` 兑换 Token。 6. 使用 Access Token 调用用户资料、会员和模型接口。 推荐 Scope: ```text openid identity membership.read points.read points.spend billing.initiate ``` Token 端点要求 HTTP Basic `client_id:client_secret`,JSON 请求体仍携带同一 `client_id`。`id_token` 只用于验证登录结果,不能作为 Bearer Token 调用 API。 ### 平台首方登录接口边界 `POST /v1/auth/login` 和 `POST /v1/auth/privacy-consents` 只用于平台自己的同源前端。它们通过 HttpOnly Cookie 建立或使用首方 Session,并受 Origin / Fetch Metadata 校验;第三方应用不要直接调用,应跳转 `/oauth/authorize`。该 Web 页面记录同意后会内部调用 `/v1/oauth/authorize` 完成授权码签发。 平台首方前端可用 `GET /v1/auth/session` 检查登录状态,并用 `GET /v1/auth/account-summary` 读取本人会员和钱包摘要。 ## 4. 读取用户与会员 ```http GET /v1/oauth/userinfo Authorization: Bearer ``` 需要 `identity` Scope。应用必须使用 `issuer + sub` 关联自己的本地账号,不得用手机号、昵称或邮箱作为唯一键。 ```http GET /v1/user/membership Authorization: Bearer ``` 需要 `membership.read` Scope,仅返回实时 `member_level`。 ```http GET /v1/user/membership/details Authorization: Bearer ``` 需要 `membership.read` Scope,返回当前等级、套餐权益、当前购买周期和预约周期。会员结果来自当前数据库状态,不使用 Access Token 内的等级快照;响应禁止缓存。 第三方应用只能读取当前授权用户的会员状态。会员升级、续费和降级由平台会员中心处理,当前不开放修改会员等级的开发者 API。 ### 主体隔离与钱包 `sub` 是按 `client_id` 隔离的随机主体,同一用户在不同应用得到不同的值。应用只能使用 `issuer + sub` 关联自己的账号;它不是平台用户 ID。平台不提供会员列表、导出、按手机号/邮箱/用户名/用户 ID 查询或跨应用同步能力。 ```http GET /v1/user/wallet Authorization: Bearer ``` 该接口需要 `points.read`,只返回当前令牌用户的 `sub`、`member_level`、`wallet.balance`、`wallet.frozen`、`wallet.available` 和更新时间,响应禁止缓存。 ## 5. 应用支付入口与归因 先在开发者中心把精确回跳地址配置到 Client 的 `billing_return_uris`,再由当前用户令牌创建一次性入口。应用不得传递或伪造 `user_id`、手机号、Client 归属或返佣参数。 ### 5.1 充值积分 创建 `recharge` 入口,平台页面负责展示充值套餐、自定义金额和支付渠道: ```http POST /v1/user/billing-entries Authorization: Bearer Content-Type: application/json { "intent": "recharge", "return_uri": "https://app.example.com/payment-complete" } ``` 支付完成并核验入口状态后,调用 `GET /v1/user/wallet` 读取实际到账余额。详细说明见 `/docs/billing/recharge`。 ### 5.2 升级会员 创建 `membership` 入口,平台页面负责展示可购买会员方案并处理升级或续费: ```http POST /v1/user/billing-entries Authorization: Bearer Content-Type: application/json { "intent": "membership", "return_uri": "https://app.example.com/payment-complete" } ``` 支付完成并核验入口状态后,调用 `GET /v1/user/membership/details` 读取实时会员等级和周期。详细说明见 `/docs/billing/membership`。 ### 5.3 通用回跳与归因流程 需要 `billing.initiate`。响应给出 `entry_id`、短时单次 `billing_url` 与过期时间。把浏览器跳到该 URL,用户在平台首方页面完成支付。 平台支付成功后仅回跳已白名单的地址,并带 `billing_entry` 和 `status=paid`。应用用同一当前用户的 Access Token 查询单个入口: ```http GET /v1/user/billing-entries/{entry_id} Authorization: Bearer ``` 不支持会员或支付入口的列表、筛选和跨用户查询。平台将归因写入会员实收、充值实收、确认积分消费和退款分项,第一阶段仅供线下结算核对,不实现自动提现、分账或返佣。 ```text 应用后端 --当前用户 Access Token--> POST /v1/user/billing-entries 应用前端 --浏览器跳转 billing_url--> IdP 登录/会员购买/积分充值页 IdP --仅回跳白名单 return_uri--> 应用前端(billing_entry + status 提示) 应用后端 --同一用户 Access Token--> GET /v1/user/billing-entries/{entry_id} IdP --HMAC Webhook--> 应用服务端(通知;最终状态仍以上述查询为准) ``` ## 6. 调用模型与积分扣减 终端用户场景使用带 `points.spend` Scope 的 Access Token: ```http POST /v1/models/{modelId}/invoke Authorization: Bearer Content-Type: application/json { "idempotency_key": "user-request-20260819-001", "input": { "messages": [{ "role": "user", "content": "概括这段内容" }] } } ``` 模型网关会自动完成积分结算,调用方不需要手工扣分: 1. 按 Client 模型定价从当前调用用户钱包预扣,余额转为冻结;余额不足返回 `402 insufficient_balance`。 2. 校验会员等级、开发者等级、内容安全和信用额度后调用供应商。 3. 成功时确认消费;明确失败时释放并退回积分。 4. 供应商结果未知时释放终端用户积分并返回 `provider_unknown`,平台进入人工核对。 5. 后置内容安全拦截时自动退款。 不要为模型调用自行编排 `/v1/points/reserve`、`/confirm`、`/release`。这些是通用积分原语,模型网关已在内部完成全部步骤。 每次新业务调用生成唯一 `idempotency_key`;同一次业务重试必须复用原键和完全相同的请求体。 ## 7. 签名 Webhook 每个 Client 可以在开发者中心配置一个 HTTPS Webhook: ```http PUT /v1/developer/clients/{client_id}/webhook Cookie: <平台首方会话> Content-Type: application/json { "url": "https://app.example.com/idp/webhook", "enabled": true, "rotate_secret": false } ``` 新建或主动轮换时返回一次 `webhook_secret`,只能保存在应用服务端。事件包括 `billing.payment.paid`、`billing.payment.refunded`、`membership.changed`、`points.confirmed`、`points.released`。 平台发送 `X-IdP-Event-Id`、`X-IdP-Timestamp`、`X-IdP-Signature: v1=`。应用按 `HMAC-SHA256(webhook_secret, ".")` 验签,校验时间窗口,并以 `event_id` 持久化去重。2xx 视为成功;其他响应或超时最多重试 8 次。 ```js import { createHmac, timingSafeEqual } from "node:crypto"; export function verifyIdpWebhook({ rawBody, timestamp, signature, secret }) { if (!/^\d+$/.test(timestamp) || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false; if (!/^v1=[0-9a-f]{64}$/.test(signature)) return false; const expected = Buffer.from(createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest("hex"), "hex"); const received = Buffer.from(signature.slice(3), "hex"); return timingSafeEqual(expected, received); } ``` 必须对 HTTP 原始正文验签,成功后再解析 JSON,并以 `event_id` 唯一约束去重。 负载仅含当前 `client_id`、应用专属 `sub`、业务金额/积分/状态和事件元数据,不含内部用户 ID、手机号、邮箱、Token、密码、Secret 或完整钱包流水。 ## 8. Token、Secret 与重试安全 - Client Secret 和 API Key 明文都只展示一次,只保存在服务端秘密管理系统。 - `state`、`nonce` 和 `code_verifier` 保存在服务端会话,完成或失败后立即删除。 - Refresh Token 每次使用都会轮转;调用方必须原子替换旧值。 - `409 in_progress`:稍后复用同一幂等键和相同请求体。 - `409 idempotency_key_conflict`:同一键对应了不同请求体,属于调用方错误。 - `200 status=provider_unknown`:保存 `request_id`,不要更换幂等键自动重试。 - `401 invalid_api_key`:停止重试并检查密钥是否未确认交付、已过期或已吊销。 - `402`:余额不足或开发者信用额度已触发熔断,不得无限重试。 - `422 content_blocked`:根据 `details.stage` 判断前置拦截或后置退款。 - `429 rate_limited`:遵循 `Retry-After`,使用有上限的指数退避。 - `503 model_providers_disabled`:模型总开关关闭,不会预扣积分或访问供应商。 ## 9. 金额与货币单位 - 平台全站统一使用人民币(`CNY` / `¥`)元。 - API 和账本中的 `cny_cents`、`*_cents` 使用人民币分,并以十进制字符串传输;例如 `"1999"` 表示 `¥19.99`。 - 接入方不得使用 JavaScript `number` 做金额运算。 本文件只使用占位配置,不包含真实 Client、Token、API Key、商户或供应商凭证。 ## 6. OpenAPI 3.1 完整契约 规范版本:`3.1.0`;当前路径数:`40`。 ```json { "openapi": "3.1.0", "info": { "title": "IP 中台服务平台开发者 API", "version": "1.1.0-dual-credential-v1.17", "description": "Phase 1 已批准的 OAuth/API Key 双凭据契约。资金和 ID 字段以十进制字符串传输;OAuth2/OIDC 错误遵循 RFC,其余错误使用 error/message。API Key 创建时绑定唯一 Client,确认一次性交付后才可调用模型;功能开关未开放时返回 503。" }, "servers": [ { "url": "/", "description": "当前部署" } ], "tags": [ { "name": "OAuth" }, { "name": "Identity" }, { "name": "AI Marketplace" }, { "name": "Developer Documentation" }, { "name": "Client Management" }, { "name": "Billing" }, { "name": "Webhooks" }, { "name": "Models" }, { "name": "Developer API Key" }, { "name": "Sandbox" }, { "name": "Admin API Key" }, { "name": "Points" }, { "name": "Payments" } ], "paths": { "/v1/oauth/authorize": { "get": { "tags": [ "OAuth" ], "summary": "Authorization Code + PKCE 协议执行(由 IdP Web 授权页在隐私同意后调用)", "description": "第三方浏览器入口是 GET /oauth/authorize。该 IdP Web 页面在首方 Session 下展示当前隐私政策、记录 OAuth 同意后调用本端点;第三方应用不得直接调用隐私同意接口。", "parameters": [ { "name": "response_type", "in": "query", "required": true, "schema": { "const": "code" } }, { "name": "client_id", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "redirect_uri", "in": "query", "required": true, "schema": { "type": "string", "format": "uri" } }, { "name": "scope", "in": "query", "required": true, "schema": { "type": "string" } }, { "name": "code_challenge", "in": "query", "required": true, "schema": { "type": "string", "minLength": 43, "maxLength": 128 } }, { "name": "code_challenge_method", "in": "query", "required": true, "schema": { "const": "S256" } }, { "name": "state", "in": "query", "schema": { "type": "string" } }, { "name": "nonce", "in": "query", "description": "scope 包含 openid 时必填", "schema": { "type": "string" } } ], "responses": { "302": { "description": "重定向到已登记 redirect_uri,并携带 code/state 或 OAuth 错误" }, "400": { "$ref": "#/components/responses/OAuthError" }, "403": { "$ref": "#/components/responses/OAuthError" } } } }, "/v1/oauth/token": { "post": { "tags": [ "OAuth" ], "summary": "兑换或刷新 Token", "security": [ { "clientBasic": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/AuthorizationCodeGrant" }, { "$ref": "#/components/schemas/RefreshTokenGrant" } ] } } } }, "responses": { "200": { "description": "Token 签发成功", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OAuthToken" } } } }, "400": { "$ref": "#/components/responses/OAuthError" }, "401": { "$ref": "#/components/responses/OAuthError" } } } }, "/v1/oauth/userinfo": { "get": { "tags": [ "Identity" ], "summary": "读取标准化用户资料", "security": [ { "oauthAccessToken": [] } ], "responses": { "200": { "description": "用户资料", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserInfo" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" } } } }, "/v1/oauth/revoke": { "post": { "tags": [ "OAuth" ], "summary": "撤销当前用户对 Client 的授权", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false } } } }, "responses": { "200": { "description": "撤销成功", "content": { "application/json": { "schema": { "type": "object", "properties": { "revoked": { "const": true } }, "required": [ "revoked" ] } } } }, "401": { "$ref": "#/components/responses/AppError" } } } }, "/v1/user/membership": { "get": { "tags": [ "Identity" ], "summary": "读取实时会员等级", "security": [ { "oauthAccessToken": [] } ], "responses": { "200": { "description": "会员等级", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } } }, "content": { "application/json": { "schema": { "type": "object", "properties": { "member_level": { "type": "integer", "minimum": 0, "maximum": 4 } }, "required": [ "member_level" ] } } } }, "401": { "$ref": "#/components/responses/MembershipAppError" }, "403": { "$ref": "#/components/responses/MembershipAppError" }, "429": { "$ref": "#/components/responses/MembershipRateLimitError" }, "503": { "$ref": "#/components/responses/MembershipAppError" } } } }, "/v1/user/membership/details": { "get": { "tags": [ "Identity" ], "summary": "读取当前会员详情", "description": "读取实时会员等级、套餐权益、当前购买周期和预约降级周期。需要 membership.read scope;开放会员 API 总开关关闭时返回 503。", "security": [ { "oauthAccessToken": [] } ], "responses": { "200": { "description": "会员详情", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MembershipDetails" } } } }, "401": { "$ref": "#/components/responses/MembershipAppError" }, "403": { "$ref": "#/components/responses/MembershipAppError" }, "429": { "$ref": "#/components/responses/MembershipRateLimitError" }, "503": { "$ref": "#/components/responses/MembershipAppError" } } } }, "/v1/user/wallet": { "get": { "tags": [ "Identity" ], "summary": "读取当前授权用户的钱包摘要", "description": "需要 points.read。只返回当前 Access Token 对应主体;不提供会员或钱包列表、搜索、导出及跨用户查询。", "security": [ { "oauthAccessToken": [] } ], "responses": { "200": { "description": "当前主体钱包摘要", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WalletSummary" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/user/billing-entries": { "post": { "tags": [ "Billing" ], "summary": "为当前授权用户创建一次性支付入口", "description": "需要 billing.initiate。return_uri 必须精确匹配 Client 的 billing_return_uris;请求不接受 user_id、手机号、Client 归属或返佣字段。", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BillingEntryRequest" } } } }, "responses": { "201": { "description": "短时单次支付入口", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BillingEntryCreateResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/user/billing-entries/{entryId}": { "get": { "tags": [ "Billing" ], "summary": "读取当前应用、当前用户的单个支付入口状态", "description": "需要 billing.initiate。不提供列表接口,且不能查询其他应用或其他用户的入口。", "security": [ { "oauthAccessToken": [] } ], "parameters": [ { "name": "entryId", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/BigIntString" } } ], "responses": { "200": { "description": "入口及其绑定订单状态", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BillingEntryStatus" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/platform/apps": { "get": { "tags": [ "AI Marketplace" ], "summary": "分页读取 AI 超市已上架应用", "description": "公开只读目录,只返回当前 active 应用的展示与跳转字段。不会返回开发者身份、回调白名单、支付回跳白名单、授权范围、Secret 或会员数据。", "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "page_size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } } ], "responses": { "200": { "description": "已上架应用分页", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PublicApplicationPage" } } } }, "400": { "$ref": "#/components/responses/AppError" } } } }, "/v1/platform/models": { "get": { "tags": [ "Developer Documentation" ], "summary": "读取公开模型文档索引", "description": "公开只读目录,只返回模型与供应商均启用且供应商未删除的文档展示字段。不会返回供应商、成本、应用定价、会员门槛或凭证。", "responses": { "200": { "description": "可公开访问的模型文档索引", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PublicModelCatalog" } } } } } } }, "/v1/platform/models/pricing": { "get": { "tags": [ "Developer Documentation" ], "summary": "读取公开模型默认价格", "description": "公开只读价格目录,只返回当前可用模型的默认积分价格、计费单位与平台积分汇率。不会返回供应商、平台成本、Client 覆盖价格、会员门槛或凭证。", "responses": { "200": { "description": "当前可用模型的默认价格", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PublicModelPricingCatalog" } } } } } } }, "/v1/platform/models/documentation": { "get": { "tags": [ "Developer Documentation" ], "summary": "读取实时模型 Markdown 索引", "description": "为 LLM、代码代理和开发者实时生成当前可用模型的 Markdown 索引。内容包含逐模型 Markdown 链接,不包含供应商、成本、应用定价、会员门槛或凭证。", "responses": { "200": { "description": "实时模型 Markdown 索引", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "text/markdown": { "schema": { "type": "string" } } } } } } }, "/v1/platform/models/{modelKey}/documentation": { "get": { "tags": [ "Developer Documentation" ], "summary": "读取单个模型的实时 Markdown 调用文档", "description": "仅为当前模型与供应商均启用且供应商未删除的模型生成文档。包含 API Key 与 OAuth 两种调用模式,以及 cURL、Node.js、Python 示例。", "parameters": [ { "name": "modelKey", "in": "path", "required": true, "schema": { "type": "string", "maxLength": 64 } } ], "responses": { "200": { "description": "单模型实时 Markdown 调用文档", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } } }, "content": { "text/markdown": { "schema": { "type": "string" } } } }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/clients": { "get": { "tags": [ "Client Management" ], "summary": "分页读取本人应用", "description": "只返回当前开发者名下的应用配置。client_secret_masked 仅表示 Secret 是否已配置,不包含明文、后缀或哈希。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "page_size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } } ], "responses": { "200": { "description": "本人应用分页", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientApplicationPage" } } } }, "401": { "$ref": "#/components/responses/AppError" } } }, "post": { "tags": [ "Client Management" ], "summary": "已停用的直接正式应用创建入口", "description": "新应用必须先通过 POST /v1/developer/sandboxes 创建测试库并完成基础联调,再从 Sandbox 提交正式上线申请。该旧路径保留用于向旧调用方稳定返回 409 sandbox_required,不创建任何数据。", "security": [ { "sessionCookie": [] } ], "responses": { "401": { "$ref": "#/components/responses/AppError" }, "409": { "description": "sandbox_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppError" } } } } } } }, "/v1/developer/clients/{clientId}": { "patch": { "tags": [ "Client Management" ], "summary": "修改本人应用配置", "description": "名称和最低会员等级直接生效。修改首页、授权范围、OAuth 回调或支付回跳白名单时,应用转为 pending 并从 AI 超市下架,旧 Refresh Token 与 Client Secret 失效。Sandbox 正式申请被拒后不得通过本端点原地回审,须从原 Sandbox 重新申请,返回 409 sandbox_required。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientConfigUpdateRequest" } } } }, "responses": { "200": { "description": "保存后的应用配置及重新审核标记", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientConfigUpdate" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/clients/{clientId}/secret": { "get": { "tags": [ "Client Management" ], "summary": "领取审核签发的 Client Secret", "description": "仅 Active 应用、仅可信首方浏览器可调用;明文使用 GETDEL 语义只返回一次,响应禁止缓存。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "一次性明文", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientSecretResponse" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/clients/{clientId}/rotate-secret": { "post": { "tags": [ "Client Management" ], "summary": "轮换 Client Secret", "description": "仅 Active 或 Disabled 应用可轮换。旧 Secret 在事务提交后立即失效,新明文只在本次响应返回。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false } } } }, "responses": { "200": { "description": "轮换后的一次性明文", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientSecretResponse" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/clients/{clientId}/webhook": { "get": { "tags": [ "Webhooks" ], "summary": "读取本人 Client 的 Webhook 脱敏配置", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string" } } ], "responses": { "200": { "description": "配置元数据,不返回密钥;尚未配置时为 null", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/WebhookConfig" }, { "type": "null" } ] } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } }, "put": { "tags": [ "Webhooks" ], "summary": "配置或轮换本人 Client 的 Webhook", "description": "新建或 rotate_secret=true 时,webhook_secret 只在本次响应中返回;之后不能读取明文。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfigRequest" } } } }, "responses": { "200": { "description": "保存后的配置;密钥仅在新建或轮换时出现", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfig" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes": { "get": { "tags": [ "Sandbox" ], "summary": "分页读取本人 Sandbox 控制面状态", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "page_size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } } ], "responses": { "200": { "description": "Sandbox 状态分页", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxPage" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" } } }, "post": { "tags": [ "Sandbox" ], "summary": "创建或从本人正式 Client 复制 Sandbox", "description": "不创建 Admin 审核任务,不进入 AI 超市。request_id 重放只返回同一 Sandbox,不会创建第二个。", "security": [ { "sessionCookie": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxCreateRequest" } } } }, "responses": { "200": { "description": "创建意图重放", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "202": { "description": "已排队初始化", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}": { "get": { "tags": [ "Sandbox" ], "summary": "读取单个 Sandbox 状态", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "responses": { "200": { "description": "Sandbox 状态", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/configuration": { "put": { "tags": [ "Sandbox" ], "summary": "以版本锁修改 Sandbox 配置", "description": "响应不确定时复用原 request_id、config_version 和 configuration;服务端优先返回同请求重放,再判断版本冲突。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxConfigurationUpdateRequest" } } } }, "responses": { "200": { "description": "更新后的状态", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/resume": { "post": { "tags": [ "Sandbox" ], "summary": "恢复并重新校验既有 Sandbox", "description": "同一恢复意图的网络重试必须复用原 request_id。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxRequestId" } } } }, "responses": { "200": { "description": "幂等重放或已经可用", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "202": { "description": "恢复校验已排队", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/pause": { "post": { "tags": [ "Sandbox" ], "summary": "二次确认暂停 Sandbox", "description": "同一暂停意图的网络重试必须复用原 request_id。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxPauseRequest" } } } }, "responses": { "200": { "description": "已暂停;不删除测试数据", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxStatus" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/credentials": { "get": { "tags": [ "Sandbox" ], "summary": "读取当前有效的 Sandbox 测试凭据", "description": "仅 Active Sandbox 的所有者可在 Trusted Browser 中读取;当前有效凭据可重复读取,暂停时隐藏并拒绝读取。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "responses": { "200": { "description": "当前有效凭据包,可在失效前重复读取", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxCredentialPack" } } } }, "401": { "$ref": "#/components/responses/SandboxCredentialError" }, "404": { "$ref": "#/components/responses/SandboxCredentialError" }, "409": { "$ref": "#/components/responses/SandboxCredentialError" }, "503": { "$ref": "#/components/responses/SandboxCredentialError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/credentials/regenerate": { "post": { "tags": [ "Sandbox" ], "summary": "废止旧测试凭据并持久保存新凭据", "description": "同一轮换意图的网络重试必须复用原 request_id;重放返回交付状态,随后可通过 GET 读取当前有效凭据。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxRequestId" } } } }, "responses": { "200": { "description": "相同请求重放;客户端再读取当前有效包", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxCredentialDeliveryReplay" } } } }, "201": { "description": "已加密持久保存的新凭据包", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxCredentialPack" } } } }, "401": { "$ref": "#/components/responses/SandboxCredentialError" }, "404": { "$ref": "#/components/responses/SandboxCredentialError" }, "409": { "$ref": "#/components/responses/SandboxCredentialError" }, "503": { "$ref": "#/components/responses/SandboxCredentialError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/webhook": { "get": { "tags": [ "Sandbox", "Webhooks" ], "summary": "读取 Sandbox Webhook 配置", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "responses": { "200": { "description": "配置元数据,不返回 Secret;未配置时为 null", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/WebhookConfig" }, { "type": "null" } ] } } } }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } }, "put": { "tags": [ "Sandbox", "Webhooks" ], "summary": "配置或轮换 Sandbox Webhook", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfigRequest" } } } }, "responses": { "200": { "description": "配置元数据;Secret 仅在新建或轮换时返回", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookConfig" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/sandboxes/{sandboxClientId}/production-applications": { "post": { "tags": [ "Sandbox", "Client Management" ], "summary": "由已完成联调的 Sandbox 快照创建正式 pending Client", "description": "仅 Sandbox active 且 basic_integration_completed=true 时可提交。同一申请意图的网络重试必须复用原 request_id。只复制不可变的公开应用配置;测试用户、凭据、Webhook、模型、支付和日志绝不迁移。未完成联调返回 409 sandbox_integration_incomplete。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "sandboxClientId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^sbx_" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxRequestId" } } } }, "responses": { "200": { "description": "相同申请重放", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxProductionApplication" } } } }, "201": { "description": "已创建既有 pending Client", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SandboxProductionApplication" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/models/{modelId}/invoke": { "post": { "tags": [ "Models" ], "summary": "幂等调用模型网关", "description": "平台内部完成内容安全、积分预扣、开发者信用预占以及确认或释放。相同幂等键必须对应相同请求体。", "security": [ { "oauthAccessToken": [] } ], "parameters": [ { "name": "modelId", "in": "path", "required": true, "description": "模型目录返回的稳定 model_key", "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvokeRequest" } } } }, "responses": { "200": { "description": "已收敛的网关结果;provider_unknown 表示已进入人工核对", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvokeResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "402": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "413": { "$ref": "#/components/responses/AppError" }, "422": { "$ref": "#/components/responses/AppError" }, "429": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/api-keys": { "get": { "tags": [ "Developer API Key" ], "summary": "分页读取本人 API Key", "description": "只返回脱敏元数据、绑定 Client、Scope、交付和到期状态,不返回完整明文或哈希。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "client_id", "in": "query", "schema": { "type": "string", "maxLength": 64 } }, { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1, "default": 1 } }, { "name": "page_size", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } } ], "responses": { "200": { "description": "API Key 分页", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperApiKeyPage" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } }, "post": { "tags": [ "Developer API Key" ], "summary": "创建绑定 Client 的 API Key", "description": "首次 201 只返回一次完整明文,初始状态为 delivery_pending;保存后必须调用 confirm-delivery 激活。相同创建意图重放不再返回明文。", "security": [ { "sessionCookie": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateDeveloperApiKeyRequest" } } } }, "responses": { "200": { "description": "相同创建意图重放,无 api_key", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperApiKeyCreateResponse" } } } }, "201": { "description": "首次创建,api_key 仅本次返回", "headers": { "Cache-Control": { "schema": { "const": "no-store" } }, "Pragma": { "schema": { "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperApiKeyCreateResponse" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/api-keys/{id}/confirm-delivery": { "post": { "tags": [ "Developer API Key" ], "summary": "确认一次性明文已安全保存", "description": "未过期 delivery_pending Key 幂等激活;响应永不返回完整明文。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/BigIntString" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false } } } }, "responses": { "200": { "description": "确认结果", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperApiKeyDeliveryResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/api-keys/{id}/revoke": { "post": { "tags": [ "Developer API Key" ], "summary": "幂等吊销本人 API Key", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "$ref": "#/components/schemas/BigIntString" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false } } } }, "responses": { "200": { "description": "吊销结果", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperApiKeyRevokeResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/developer/models/{modelKey}/invoke": { "post": { "tags": [ "Developer API Key", "Models" ], "summary": "使用开发者 API Key 幂等调用模型", "description": "从唯一 X-API-Key 识别开发者与绑定 Client,以开发者本人作为积分账户。client_id 省略时仍使用 Key 绑定值;若携带则只作一致性断言,不能切换 Client。平台内部完成内容安全、积分预扣、开发者信用预占以及确认或释放。", "security": [ { "developerApiKey": [] } ], "parameters": [ { "name": "modelKey", "in": "path", "required": true, "schema": { "type": "string" } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DeveloperInvokeRequest" } } } }, "responses": { "200": { "description": "已收敛的网关结果;provider_unknown 表示已进入人工核对", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/InvokeResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "402": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "413": { "$ref": "#/components/responses/AppError" }, "422": { "$ref": "#/components/responses/AppError" }, "429": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/admin/clients/{clientId}/environments": { "get": { "tags": [ "Admin Clients" ], "summary": "读取应用的正式与测试环境脱敏元数据", "description": "需要 client.view。只返回环境、配置和凭据交付状态;不返回 Secret 明文、哈希、可逆密文、API Key 或测试账号密码。纯 Sandbox 应用仍不进入正式 Client 列表。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "maxLength": 64 } } ], "responses": { "200": { "description": "双环境脱敏元数据", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AdminClientEnvironments" } } } }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" } } } }, "/v1/admin/clients/{clientId}/api-key-policy": { "patch": { "tags": [ "Admin API Key" ], "summary": "设置单个 Client 的 API Key 模型调用策略", "description": "需要同时具备 developer_api_key.policy 与 developer_api_key.view。仅 active Client 可设置;重复设置仍记录 changed=false 审计。Client 停用时策略自动关闭,重新启用不会自动恢复。", "security": [ { "sessionCookie": [] } ], "parameters": [ { "name": "clientId", "in": "path", "required": true, "schema": { "type": "string", "minLength": 1, "maxLength": 64 } } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientApiKeyPolicyRequest" } } } }, "responses": { "200": { "description": "当前策略结果", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClientApiKeyPolicyResult" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "403": { "$ref": "#/components/responses/AppError" }, "404": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/points/reserve": { "post": { "tags": [ "Points" ], "summary": "通用积分预扣原语", "description": "普通模型调用应优先使用 /v1/models/{modelId}/invoke,不应自行编排三段式端点。", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ReserveRequest" } } } }, "responses": { "200": { "$ref": "#/components/responses/Reservation" }, "201": { "$ref": "#/components/responses/Reservation" }, "400": { "$ref": "#/components/responses/AppError" }, "402": { "$ref": "#/components/responses/AppError" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/points/confirm": { "post": { "tags": [ "Points" ], "summary": "确认积分预扣", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "$ref": "#/components/requestBodies/IdempotencyKey" }, "responses": { "200": { "$ref": "#/components/responses/Reservation" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/points/release": { "post": { "tags": [ "Points" ], "summary": "释放积分预扣", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "$ref": "#/components/requestBodies/IdempotencyKey" }, "responses": { "200": { "$ref": "#/components/responses/Reservation" }, "409": { "$ref": "#/components/responses/AppError" } } } }, "/v1/payments/recharge": { "post": { "tags": [ "Payments" ], "summary": "创建积分充值订单", "description": "生产支付必须由平台显式启用;订单归因取当前 Access Token 的 client_id,应用不得传入归属字段、自行伪造或调用支付回调。新接入推荐使用一次性 billing-entry 页面完成支付。", "security": [ { "oauthAccessToken": [] } ], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RechargeRequest" } } } }, "responses": { "201": { "description": "充值订单", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentOrder" } } } }, "400": { "$ref": "#/components/responses/AppError" }, "401": { "$ref": "#/components/responses/AppError" }, "503": { "$ref": "#/components/responses/AppError" } } } }, "/v1/.well-known/jwks.json": { "get": { "tags": [ "OAuth" ], "summary": "读取 JWT 公钥集合", "responses": { "200": { "description": "JSON Web Key Set" } } } } }, "components": { "securitySchemes": { "oauthAccessToken": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "只接受 access_token,不接受 id_token" }, "clientBasic": { "type": "http", "scheme": "basic", "description": "用户名为 client_id,密码为当前 Client Secret;轮换后旧 Secret 立即失效" }, "developerApiKey": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "在开发者中心创建并确认交付的 API Key,只用于 /v1/developer/models/*;明文只显示一次" }, "sessionCookie": { "type": "apiKey", "in": "cookie", "name": "__Host-idp_session", "description": "平台首方登录 Session;写操作同时受可信浏览器来源校验" } }, "schemas": { "BigIntString": { "type": "string", "pattern": "^[0-9]+$", "examples": [ "100" ] }, "AuthorizationCodeGrant": { "type": "object", "additionalProperties": false, "required": [ "grant_type", "code", "code_verifier", "redirect_uri", "client_id" ], "properties": { "grant_type": { "const": "authorization_code" }, "code": { "type": "string" }, "code_verifier": { "type": "string", "minLength": 43, "maxLength": 128 }, "redirect_uri": { "type": "string", "format": "uri" }, "client_id": { "type": "string" } } }, "RefreshTokenGrant": { "type": "object", "additionalProperties": false, "required": [ "grant_type", "refresh_token", "client_id" ], "properties": { "grant_type": { "const": "refresh_token" }, "refresh_token": { "type": "string" }, "client_id": { "type": "string" } } }, "OAuthToken": { "type": "object", "required": [ "access_token", "token_type", "expires_in", "refresh_token", "scope" ], "properties": { "access_token": { "type": "string" }, "token_type": { "const": "Bearer" }, "expires_in": { "type": "integer" }, "refresh_token": { "type": "string" }, "scope": { "type": "string" }, "id_token": { "type": "string" } } }, "UserInfo": { "type": "object", "required": [ "sub", "member_level" ], "properties": { "sub": { "$ref": "#/components/schemas/PairwiseSubject" }, "phone_number": { "type": [ "string", "null" ], "description": "如有值则为脱敏手机号" }, "member_level": { "type": "integer", "minimum": 0, "maximum": 4 } } }, "PairwiseSubject": { "type": "string", "pattern": "^usr_[A-Za-z0-9_-]+$", "description": "按 client_id 隔离的随机主体标识,不是平台用户 ID" }, "MembershipSubscriptionView": { "type": "object", "required": [ "id", "member_level", "status", "effective_at", "period_start_at", "period_end_at" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "status": { "enum": [ "active", "scheduled" ] }, "effective_at": { "type": "string", "format": "date-time" }, "period_start_at": { "type": "string", "format": "date-time" }, "period_end_at": { "type": "string", "format": "date-time" } } }, "MembershipPlanView": { "type": "object", "required": [ "member_level", "name", "description", "monthly_points_included", "max_authorized_apps", "benefits" ], "properties": { "member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "name": { "type": "string" }, "description": { "type": [ "string", "null" ] }, "monthly_points_included": { "$ref": "#/components/schemas/BigIntString" }, "max_authorized_apps": { "type": [ "integer", "null" ], "minimum": 0 }, "benefits": { "type": "array", "items": { "type": "string" } } } }, "MembershipDetails": { "type": "object", "required": [ "member_level", "plan", "current_subscription", "scheduled_subscription" ], "properties": { "member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "plan": { "oneOf": [ { "$ref": "#/components/schemas/MembershipPlanView" }, { "type": "null" } ] }, "current_subscription": { "oneOf": [ { "$ref": "#/components/schemas/MembershipSubscriptionView" }, { "type": "null" } ] }, "scheduled_subscription": { "oneOf": [ { "$ref": "#/components/schemas/MembershipSubscriptionView" }, { "type": "null" } ] } } }, "WalletSummary": { "type": "object", "additionalProperties": false, "required": [ "sub", "member_level", "wallet" ], "properties": { "sub": { "$ref": "#/components/schemas/PairwiseSubject" }, "member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "wallet": { "type": "object", "additionalProperties": false, "required": [ "balance", "frozen", "available", "updated_at" ], "properties": { "balance": { "$ref": "#/components/schemas/BigIntString" }, "frozen": { "$ref": "#/components/schemas/BigIntString" }, "available": { "$ref": "#/components/schemas/BigIntString" }, "updated_at": { "type": [ "string", "null" ], "format": "date-time" } } } } }, "BillingEntryRequest": { "type": "object", "additionalProperties": false, "required": [ "intent", "return_uri" ], "properties": { "intent": { "enum": [ "membership", "recharge" ] }, "return_uri": { "type": "string", "format": "uri", "maxLength": 2048 } } }, "BillingEntryCreateResult": { "type": "object", "additionalProperties": false, "required": [ "entry_id", "billing_url", "expires_at" ], "properties": { "entry_id": { "$ref": "#/components/schemas/BigIntString" }, "billing_url": { "type": "string", "format": "uri", "description": "15 分钟有效的一次性不透明入口" }, "expires_at": { "type": "string", "format": "date-time" } } }, "BillingEntryStatus": { "type": "object", "additionalProperties": false, "required": [ "id", "intent", "status", "expires_at", "payment_order" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "intent": { "enum": [ "membership", "recharge" ] }, "status": { "enum": [ "pending", "consumed", "expired" ] }, "expires_at": { "type": "string", "format": "date-time" }, "payment_order": { "oneOf": [ { "type": "null" }, { "type": "object", "additionalProperties": false, "required": [ "status", "paid_at", "refunded_at" ], "properties": { "status": { "type": "string" }, "paid_at": { "type": [ "string", "null" ], "format": "date-time" }, "refunded_at": { "type": [ "string", "null" ], "format": "date-time" } } } ] } } }, "ClientScope": { "type": "string", "enum": [ "openid", "identity", "membership.read", "points.read", "points.spend", "billing.initiate" ], "description": "openid=OIDC 登录身份;identity=基础身份;membership.read=会员等级;points.read=积分余额;points.spend=积分消费;billing.initiate=会员与充值入口" }, "ClientApplicationRequest": { "type": "object", "additionalProperties": false, "required": [ "name", "redirect_uris", "billing_return_uris", "allowed_scopes", "min_member_level" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "homepage_url": { "type": [ "string", "null" ], "format": "uri", "maxLength": 2048, "description": "AI 超市打开应用时使用的 HTTP(S) 首页或介绍页;不得包含 URL 账号密码信息" }, "redirect_uris": { "type": "array", "minItems": 1, "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "billing_return_uris": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "allowed_scopes": { "type": "array", "minItems": 1, "maxItems": 20, "uniqueItems": true, "items": { "$ref": "#/components/schemas/ClientScope" } }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 } } }, "ClientApplication": { "type": "object", "additionalProperties": false, "required": [ "client_id", "name", "homepage_url", "redirect_uris", "billing_return_uris", "allowed_scopes", "min_member_level", "status", "client_secret_masked", "created_at" ], "properties": { "client_id": { "type": "string", "description": "稳定公开应用标识,可进入浏览器和 OAuth 请求" }, "name": { "type": "string" }, "homepage_url": { "type": [ "string", "null" ], "format": "uri" }, "redirect_uris": { "type": "array", "items": { "type": "string", "format": "uri" } }, "billing_return_uris": { "type": "array", "items": { "type": "string", "format": "uri" } }, "allowed_scopes": { "type": "array", "items": { "$ref": "#/components/schemas/ClientScope" } }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "status": { "enum": [ "pending", "active", "disabled", "rejected" ] }, "reject_reason": { "type": [ "string", "null" ] }, "client_secret_masked": { "type": [ "string", "null" ], "pattern": "^\\*{16}$", "description": "固定掩码仅表示 Secret 已配置;不包含明文、后缀或哈希" }, "created_at": { "type": "string", "format": "date-time" } } }, "ClientApplicationPage": { "type": "object", "additionalProperties": false, "required": [ "items", "pagination" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/ClientApplication" } }, "pagination": { "type": "object", "additionalProperties": false, "required": [ "page", "page_size", "total", "total_pages" ], "properties": { "page": { "type": "integer" }, "page_size": { "type": "integer" }, "total": { "type": "integer" }, "total_pages": { "type": "integer" } } } } }, "SandboxConfiguration": { "type": "object", "additionalProperties": false, "required": [ "name", "homepage_url", "redirect_uris", "billing_return_uris", "allowed_scopes", "min_member_level" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "homepage_url": { "type": [ "string", "null" ], "format": "uri", "maxLength": 2048 }, "redirect_uris": { "type": "array", "minItems": 1, "maxItems": 20, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "billing_return_uris": { "type": "array", "maxItems": 20, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "allowed_scopes": { "type": "array", "minItems": 1, "maxItems": 20, "uniqueItems": true, "items": { "$ref": "#/components/schemas/ClientScope" } }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 } } }, "SandboxRequestId": { "type": "object", "additionalProperties": false, "required": [ "request_id" ], "properties": { "request_id": { "type": "string", "format": "uuid" } } }, "SandboxPauseRequest": { "type": "object", "additionalProperties": false, "required": [ "request_id", "confirmed" ], "properties": { "request_id": { "type": "string", "format": "uuid" }, "confirmed": { "const": true } } }, "SandboxCreateRequest": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "request_id", "configuration" ], "properties": { "request_id": { "type": "string", "format": "uuid" }, "configuration": { "$ref": "#/components/schemas/SandboxConfiguration" } } }, { "type": "object", "additionalProperties": false, "required": [ "request_id", "source_production_client_id" ], "properties": { "request_id": { "type": "string", "format": "uuid" }, "source_production_client_id": { "type": "string", "minLength": 1, "maxLength": 64 } } } ] }, "SandboxConfigurationUpdateRequest": { "type": "object", "additionalProperties": false, "required": [ "request_id", "config_version", "configuration" ], "properties": { "request_id": { "type": "string", "format": "uuid" }, "config_version": { "type": "integer", "minimum": 1 }, "configuration": { "$ref": "#/components/schemas/SandboxConfiguration" } } }, "SandboxStatus": { "type": "object", "required": [ "sandbox_client_id", "sandbox_status", "configuration", "config_version", "credentials_available", "first_successful_at", "last_successful_at", "basic_integration_completed", "provisioning", "production", "limits" ], "properties": { "sandbox_client_id": { "type": "string", "pattern": "^sbx_" }, "sandbox_status": { "enum": [ "provisioning", "active", "paused", "provisioning_failed" ] }, "configuration": { "$ref": "#/components/schemas/SandboxConfiguration" }, "config_version": { "type": "integer", "minimum": 1 }, "credentials_available": { "type": "boolean" }, "first_successful_at": { "type": [ "string", "null" ], "format": "date-time" }, "last_successful_at": { "type": [ "string", "null" ], "format": "date-time" }, "basic_integration_completed": { "type": "boolean" }, "provisioning": { "type": [ "object", "null" ] }, "production": { "oneOf": [ { "type": "null" }, { "type": "object", "required": [ "client_id", "status" ], "properties": { "client_id": { "type": "string" }, "status": { "enum": [ "pending", "active", "disabled", "rejected" ] } } } ] }, "limits": { "type": "object", "required": [ "test_users", "daily_mock_calls", "webhook_events_per_minute", "pending_webhook_events" ], "properties": { "test_users": { "const": 10 }, "daily_mock_calls": { "const": 200 }, "webhook_events_per_minute": { "const": 20 }, "pending_webhook_events": { "const": 100 } } } } }, "SandboxPage": { "type": "object", "additionalProperties": false, "required": [ "items", "pagination" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/SandboxStatus" } }, "pagination": { "type": "object" } } }, "SandboxCredentialPack": { "type": "object", "additionalProperties": false, "required": [ "sandbox_client_id", "client_secret", "test_accounts" ], "properties": { "sandbox_client_id": { "type": "string", "pattern": "^sbx_" }, "client_secret": { "type": "string" }, "test_accounts": { "type": "array", "minItems": 10, "maxItems": 10, "items": { "type": "object", "additionalProperties": false, "required": [ "username", "password", "member_level" ], "properties": { "username": { "type": "string" }, "password": { "type": "string" }, "member_level": { "type": "integer", "minimum": 0, "maximum": 3 } } } } } }, "SandboxCredentialDeliveryReplay": { "type": "object", "additionalProperties": false, "required": [ "delivery_replay" ], "properties": { "delivery_replay": { "const": true } } }, "SandboxProductionApplication": { "type": "object", "additionalProperties": false, "required": [ "production_client_id", "status", "snapshot_version", "created_at" ], "properties": { "production_client_id": { "type": "string" }, "status": { "const": "pending" }, "snapshot_version": { "type": "integer", "minimum": 1 }, "created_at": { "type": "string", "format": "date-time" } } }, "PublicApplication": { "type": "object", "additionalProperties": false, "required": [ "client_id", "name", "homepage_url", "min_member_level", "created_at" ], "properties": { "client_id": { "type": "string" }, "name": { "type": "string" }, "homepage_url": { "type": [ "string", "null" ], "format": "uri" }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "created_at": { "type": "string", "format": "date-time" } } }, "PublicApplicationPage": { "type": "object", "additionalProperties": false, "required": [ "items", "pagination" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/PublicApplication" } }, "pagination": { "type": "object", "additionalProperties": false, "required": [ "page", "page_size", "total", "total_pages" ], "properties": { "page": { "type": "integer" }, "page_size": { "type": "integer" }, "total": { "type": "integer" }, "total_pages": { "type": "integer" } } } } }, "PublicModel": { "type": "object", "additionalProperties": false, "required": [ "model_key", "model_name", "display_name", "model_description", "model_categories", "model_category" ], "properties": { "model_key": { "type": "string", "maxLength": 64 }, "model_name": { "type": "string", "maxLength": 100 }, "display_name": { "type": "string", "maxLength": 100 }, "model_description": { "type": [ "string", "null" ] }, "model_categories": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": [ "chat", "image", "video", "speech", "embedding", "rerank", "other" ] } }, "model_category": { "enum": [ "chat", "image", "video", "speech", "embedding", "rerank", "other" ] } } }, "PublicModelCatalog": { "type": "object", "additionalProperties": false, "required": [ "items" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/PublicModel" } } } }, "PublicModelPrice": { "type": "object", "additionalProperties": false, "required": [ "model_key", "model_name", "display_name", "model_description", "model_categories", "model_category", "billing_unit", "default_points_per_call" ], "properties": { "model_key": { "type": "string", "maxLength": 64 }, "model_name": { "type": "string", "maxLength": 100 }, "display_name": { "type": "string", "maxLength": 100 }, "model_description": { "type": [ "string", "null" ] }, "model_categories": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "enum": [ "chat", "image", "video", "speech", "embedding", "rerank", "other" ] } }, "model_category": { "enum": [ "chat", "image", "video", "speech", "embedding", "rerank", "other" ] }, "billing_unit": { "type": "string", "const": "per_call" }, "default_points_per_call": { "type": "string", "pattern": "^[1-9][0-9]*$" } } }, "PublicModelPricingCatalog": { "type": "object", "additionalProperties": false, "required": [ "billing_currency", "points_to_cny_rate", "items" ], "properties": { "billing_currency": { "type": "string", "const": "CNY" }, "points_to_cny_rate": { "type": "string", "pattern": "^[0-9]+(?:\\.[0-9]+)?$" }, "items": { "type": "array", "items": { "$ref": "#/components/schemas/PublicModelPrice" } } } }, "ClientConfigUpdateRequest": { "type": "object", "additionalProperties": false, "minProperties": 1, "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 100 }, "homepage_url": { "type": [ "string", "null" ], "format": "uri", "maxLength": 2048, "description": "显式 null 清空;active 应用变更后重新审核" }, "redirect_uris": { "type": "array", "minItems": 1, "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "billing_return_uris": { "type": "array", "maxItems": 20, "uniqueItems": true, "items": { "type": "string", "format": "uri", "maxLength": 2048 } }, "allowed_scopes": { "type": "array", "minItems": 1, "maxItems": 20, "uniqueItems": true, "items": { "$ref": "#/components/schemas/ClientScope" } }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 } } }, "ClientConfigUpdate": { "type": "object", "additionalProperties": false, "required": [ "client_id", "name", "homepage_url", "redirect_uris", "billing_return_uris", "allowed_scopes", "min_member_level", "status", "client_secret_masked", "reapproval_required" ], "properties": { "client_id": { "type": "string" }, "name": { "type": "string" }, "homepage_url": { "type": [ "string", "null" ], "format": "uri" }, "redirect_uris": { "type": "array", "items": { "type": "string", "format": "uri" } }, "billing_return_uris": { "type": "array", "items": { "type": "string", "format": "uri" } }, "allowed_scopes": { "type": "array", "items": { "$ref": "#/components/schemas/ClientScope" } }, "min_member_level": { "type": "integer", "minimum": 0, "maximum": 4 }, "status": { "enum": [ "pending", "active", "disabled", "rejected" ] }, "client_secret_masked": { "type": [ "string", "null" ], "pattern": "^\\*{16}$" }, "reapproval_required": { "type": "boolean" } } }, "ClientSecretResponse": { "type": "object", "additionalProperties": false, "required": [ "client_secret" ], "properties": { "client_secret": { "type": "string", "description": "仅本次响应出现的完整明文" } } }, "WebhookConfigRequest": { "type": "object", "additionalProperties": false, "required": [ "url" ], "properties": { "url": { "type": "string", "format": "uri", "maxLength": 2048 }, "enabled": { "type": "boolean", "default": true }, "rotate_secret": { "type": "boolean", "default": false } } }, "WebhookConfig": { "type": "object", "additionalProperties": false, "required": [ "client_id", "url", "enabled", "version", "updated_at" ], "properties": { "client_id": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "enabled": { "type": "boolean" }, "version": { "type": "integer", "minimum": 1 }, "updated_at": { "type": "string", "format": "date-time" }, "webhook_secret": { "type": [ "string", "null" ], "description": "仅在新建或主动轮换的 PUT 响应中返回一次" } } }, "InvokeRequest": { "type": "object", "additionalProperties": false, "required": [ "idempotency_key", "input" ], "properties": { "idempotency_key": { "type": "string", "minLength": 1, "maxLength": 128 }, "input": {} } }, "DeveloperInvokeRequest": { "type": "object", "additionalProperties": false, "required": [ "idempotency_key", "input" ], "properties": { "client_id": { "type": "string", "minLength": 1, "maxLength": 64, "description": "可选兼容断言;若提供必须等于 API Key 唯一绑定的 Client" }, "idempotency_key": { "type": "string", "minLength": 1, "maxLength": 128 }, "input": {} } }, "ClientApiKeyPolicyRequest": { "type": "object", "additionalProperties": false, "required": [ "enabled", "reason" ], "properties": { "enabled": { "type": "boolean" }, "reason": { "type": "string", "minLength": 1, "maxLength": 500 } } }, "ClientApiKeyPolicyResult": { "type": "object", "additionalProperties": false, "required": [ "client_id", "api_key_invoke_enabled" ], "properties": { "client_id": { "type": "string" }, "api_key_invoke_enabled": { "type": "boolean" } } }, "AdminClientEnvironments": { "type": "object", "additionalProperties": false, "required": [ "production", "sandbox" ], "properties": { "production": { "type": "object", "additionalProperties": false, "required": [ "client_id", "status", "client_secret_state" ], "properties": { "client_id": { "type": "string" }, "status": { "enum": [ "pending", "active", "disabled", "rejected" ] }, "client_secret_state": { "enum": [ "not_issued", "issued_hidden" ] } } }, "sandbox": { "oneOf": [ { "type": "null" }, { "type": "object", "additionalProperties": false, "required": [ "sandbox_client_id", "sandbox_status", "configuration", "config_version", "credentials_state", "basic_integration_completed", "first_successful_at", "last_successful_at" ], "properties": { "sandbox_client_id": { "type": "string", "pattern": "^sbx_" }, "sandbox_status": { "enum": [ "provisioning", "active", "paused", "provisioning_failed" ] }, "configuration": { "$ref": "#/components/schemas/SandboxConfiguration" }, "config_version": { "type": "integer", "minimum": 1 }, "credentials_state": { "enum": [ "not_issued", "pending_delivery", "issued_hidden", "runtime_unavailable" ] }, "basic_integration_completed": { "type": "boolean" }, "first_successful_at": { "type": [ "string", "null" ], "format": "date-time" }, "last_successful_at": { "type": [ "string", "null" ], "format": "date-time" } } } ] } } }, "CreateDeveloperApiKeyRequest": { "type": "object", "additionalProperties": false, "required": [ "name", "client_id", "create_request_id", "expires_in_days" ], "properties": { "name": { "type": "string", "minLength": 1, "maxLength": 64 }, "client_id": { "type": "string", "minLength": 1, "maxLength": 64 }, "create_request_id": { "type": "string", "format": "uuid", "description": "UUIDv4;同一创建意图重试必须复用" }, "expires_in_days": { "type": "integer", "enum": [ 30, 90, 180, 365 ] } } }, "DeveloperApiKeyListItem": { "type": "object", "required": [ "id", "name", "key_prefix", "client_id", "client_name", "scopes", "status", "effective_status", "last_used_at", "expires_at", "delivery_confirmed_at", "created_at", "revoked_at" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "name": { "type": "string" }, "key_prefix": { "type": "string" }, "client_id": { "type": [ "string", "null" ] }, "client_name": { "type": [ "string", "null" ] }, "scopes": { "type": "array", "items": { "const": "models.invoke" } }, "status": { "enum": [ "delivery_pending", "active", "revoked" ] }, "effective_status": { "enum": [ "active", "expiring_soon", "expired", "revoked", "delivery_pending", "binding_required", "scope_invalid", "client_inactive", "invoke_disabled", "developer_inactive", "credit_misconfigured" ] }, "last_used_at": { "type": [ "string", "null" ], "format": "date-time" }, "expires_at": { "type": [ "string", "null" ], "format": "date-time" }, "delivery_confirmed_at": { "type": [ "string", "null" ], "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" }, "revoked_at": { "type": [ "string", "null" ], "format": "date-time" } } }, "DeveloperApiKeyPage": { "type": "object", "required": [ "items", "pagination" ], "properties": { "items": { "type": "array", "items": { "$ref": "#/components/schemas/DeveloperApiKeyListItem" } }, "pagination": { "type": "object", "additionalProperties": false, "required": [ "page", "page_size", "total", "total_pages" ], "properties": { "page": { "type": "integer" }, "page_size": { "type": "integer" }, "total": { "type": "integer" }, "total_pages": { "type": "integer" } } } } }, "DeveloperApiKeyCreateResponse": { "type": "object", "required": [ "id", "name", "key_prefix", "client_id", "scopes", "status", "effective_status", "delivery_replay", "delivery_confirmed_at", "expires_at", "created_at" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "name": { "type": "string" }, "key_prefix": { "type": "string" }, "client_id": { "type": "string" }, "scopes": { "type": "array", "items": { "const": "models.invoke" } }, "status": { "enum": [ "delivery_pending", "active" ] }, "effective_status": { "enum": [ "delivery_pending", "active", "expiring_soon", "expired" ] }, "delivery_replay": { "type": "boolean" }, "delivery_confirmed_at": { "type": [ "string", "null" ], "format": "date-time" }, "expires_at": { "type": "string", "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" }, "api_key": { "type": "string", "description": "仅首次 201 存在" } } }, "DeveloperApiKeyDeliveryResult": { "type": "object", "additionalProperties": false, "required": [ "id", "status", "effective_status", "delivery_confirmed_at", "changed" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "status": { "const": "active" }, "effective_status": { "enum": [ "active", "expiring_soon", "expired", "client_inactive", "invoke_disabled", "developer_inactive", "credit_misconfigured" ] }, "delivery_confirmed_at": { "type": "string", "format": "date-time" }, "changed": { "type": "boolean" } } }, "DeveloperApiKeyRevokeResult": { "type": "object", "additionalProperties": false, "required": [ "id", "status", "revoked_at" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "status": { "const": "revoked" }, "revoked_at": { "type": "string", "format": "date-time" } } }, "InvokeResult": { "type": "object", "required": [ "status", "request_id" ], "properties": { "status": { "enum": [ "success", "failed", "provider_unknown" ] }, "request_id": { "type": "string" }, "error_message": { "type": "string" }, "output": {} } }, "ReserveRequest": { "type": "object", "additionalProperties": false, "required": [ "idempotency_key", "points", "platform_cost_cents" ], "properties": { "idempotency_key": { "type": "string", "maxLength": 128 }, "points": { "$ref": "#/components/schemas/BigIntString" }, "platform_cost_cents": { "$ref": "#/components/schemas/BigIntString" } } }, "Reservation": { "type": "object", "required": [ "id", "client_id", "sub", "idempotency_key", "points", "platform_cost_cents", "exchange_rate_snapshot", "status", "expires_at", "created_at", "updated_at" ], "properties": { "id": { "$ref": "#/components/schemas/BigIntString" }, "client_id": { "type": "string" }, "sub": { "$ref": "#/components/schemas/PairwiseSubject" }, "idempotency_key": { "type": "string" }, "points": { "$ref": "#/components/schemas/BigIntString" }, "platform_cost_cents": { "$ref": "#/components/schemas/BigIntString" }, "exchange_rate_snapshot": { "type": "string" }, "status": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } } }, "RechargeRequest": { "type": "object", "additionalProperties": false, "required": [ "channel", "cny_cents" ], "properties": { "channel": { "enum": [ "wechat", "alipay" ] }, "cny_cents": { "$ref": "#/components/schemas/BigIntString" } } }, "PaymentOrder": { "type": "object", "required": [ "out_trade_no", "channel", "cny_cents", "points_to_credit", "exchange_rate_snapshot", "status", "created_at", "payment_url" ], "properties": { "out_trade_no": { "type": "string" }, "channel": { "enum": [ "wechat", "alipay" ] }, "cny_cents": { "$ref": "#/components/schemas/BigIntString" }, "points_to_credit": { "$ref": "#/components/schemas/BigIntString" }, "exchange_rate_snapshot": { "type": "string" }, "status": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "paid_at": { "type": [ "string", "null" ], "format": "date-time" }, "payment_url": { "type": "string" } } }, "AppError": { "type": "object", "required": [ "error", "message" ], "properties": { "error": { "type": "string" }, "message": { "type": "string" }, "details": {} } }, "OAuthError": { "type": "object", "required": [ "error" ], "properties": { "error": { "type": "string" }, "error_description": { "type": "string" } } } }, "requestBodies": { "IdempotencyKey": { "required": true, "content": { "application/json": { "schema": { "type": "object", "additionalProperties": false, "required": [ "idempotency_key" ], "properties": { "idempotency_key": { "type": "string", "maxLength": 128 } } } } } } }, "responses": { "AppError": { "description": "应用错误", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppError" } } } }, "SandboxCredentialError": { "description": "Sandbox 凭据接口错误", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppError" } } } }, "MembershipAppError": { "description": "会员接口错误", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppError" } } } }, "MembershipRateLimitError": { "description": "会员接口限流", "headers": { "Cache-Control": { "schema": { "type": "string", "const": "no-store" } }, "Pragma": { "schema": { "type": "string", "const": "no-cache" } }, "Retry-After": { "schema": { "type": "integer", "minimum": 1 } } }, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppError" } } } }, "OAuthError": { "description": "OAuth2/OIDC 协议错误", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OAuthError" } } } }, "Reservation": { "description": "积分预扣记录", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Reservation" } } } } } } } ``` ## 7. 示例源码 ### api-key-model-invoke.mjs ```js import { randomUUID } from "node:crypto"; const issuer = process.env.IDP_ISSUER ?? "https://idp.example.com"; const apiKey = process.env.IDP_API_KEY; const modelKey = process.env.IDP_MODEL_KEY ?? "replace-with-model-key"; if (!apiKey) throw new Error("请通过环境变量 IDP_API_KEY 注入开发者 API Key"); const response = await fetch(new URL(`/v1/developer/models/${encodeURIComponent(modelKey)}/invoke`, issuer), { method: "POST", headers: { "x-api-key": apiKey, "content-type": "application/json", }, body: JSON.stringify({ idempotency_key: randomUUID(), input: { messages: [{ role: "user", content: "请回复 OK" }] }, }), }); const payload = await response.json(); if (!response.ok) throw new Error(`${payload.error ?? response.status}: ${payload.message ?? "request failed"}`); if (payload.status === "provider_unknown") { console.error(`供应商结果待平台核对,request_id=${payload.request_id};不要更换幂等键自动重试`); process.exitCode = 2; } else { console.log(JSON.stringify(payload, null, 2)); } ``` ### idp-client.mjs ```js export class IdpApiError extends Error { constructor(status, code, message, details) { super(message); this.name = "IdpApiError"; this.status = status; this.code = code; this.details = details; } } export class IdpClient { constructor({ issuer, accessToken, fetchImpl = globalThis.fetch }) { if (!issuer || !accessToken) throw new TypeError("issuer 和 accessToken 必填"); if (typeof fetchImpl !== "function") throw new TypeError("当前运行时未提供 fetch"); this.issuer = new URL(issuer); this.accessToken = accessToken; this.fetchImpl = fetchImpl; } userInfo() { return this.request("/v1/oauth/userinfo"); } membership() { return this.request("/v1/user/membership"); } membershipDetails() { return this.request("/v1/user/membership/details"); } invoke(modelKey, { idempotencyKey, input }) { if (!idempotencyKey) throw new TypeError("idempotencyKey 必填;业务重试必须复用原值"); return this.request(`/v1/models/${encodeURIComponent(modelKey)}/invoke`, { method: "POST", body: { idempotency_key: idempotencyKey, input }, }); } recharge({ channel, cnyCents }) { return this.request("/v1/payments/recharge", { method: "POST", body: { channel, cny_cents: String(cnyCents) }, }); } revoke() { return this.request("/v1/oauth/revoke", { method: "POST", body: {} }); } async request(path, { method = "GET", body } = {}) { const response = await this.fetchImpl(new URL(path, this.issuer), { method, headers: { authorization: `Bearer ${this.accessToken}`, ...(body === undefined ? {} : { "content-type": "application/json" }), }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), }); const payload = await response.json().catch(() => ({})); if (!response.ok) { throw new IdpApiError(response.status, payload.error ?? "request_failed", payload.message ?? `HTTP ${response.status}`, payload.details); } return payload; } } ``` ### model-invoke.mjs ```js import { randomUUID } from "node:crypto"; const issuer = process.env.IDP_ISSUER ?? "https://idp.example.com"; const accessToken = process.env.IDP_ACCESS_TOKEN; const modelKey = process.env.IDP_MODEL_KEY ?? "replace-with-model-key"; if (!accessToken) throw new Error("请通过环境变量 IDP_ACCESS_TOKEN 注入 OAuth access_token"); const idempotencyKey = randomUUID(); const response = await fetch(new URL(`/v1/models/${encodeURIComponent(modelKey)}/invoke`, issuer), { method: "POST", headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json", }, body: JSON.stringify({ idempotency_key: idempotencyKey, input: { messages: [{ role: "user", content: "请概括这段文本" }] }, }), }); const payload = await response.json(); if (!response.ok) throw new Error(`${payload.error ?? response.status}: ${payload.message ?? "request failed"}`); if (payload.status === "provider_unknown") { console.error(`供应商结果待平台核对,request_id=${payload.request_id};不要更换幂等键自动重试`); process.exitCode = 2; } else { console.log(JSON.stringify(payload, null, 2)); } ``` ### oauth-pkce.mjs ```js import { createHash, randomBytes } from "node:crypto"; const issuer = process.env.IDP_ISSUER ?? "https://idp.example.com"; const authorizationUiOrigin = process.env.IDP_WEB_ORIGIN ?? issuer; const clientId = process.env.IDP_CLIENT_ID ?? "replace-with-client-id"; const clientSecret = process.env.IDP_CLIENT_SECRET ?? "replace-with-client-secret"; const redirectUri = process.env.IDP_REDIRECT_URI ?? "https://app.example.com/oauth/callback"; const base64url = (value) => value.toString("base64url"); export function createAuthorizationRequest() { const verifier = base64url(randomBytes(48)); const challenge = base64url(createHash("sha256").update(verifier).digest()); const state = base64url(randomBytes(24)); const nonce = base64url(randomBytes(24)); // 浏览器必须进入 IdP 首方授权页;该页完成隐私同意后才调用 API 协议端点。 const url = new URL("/oauth/authorize", authorizationUiOrigin); url.search = new URLSearchParams({ response_type: "code", client_id: clientId, redirect_uri: redirectUri, scope: "openid identity membership.read points.spend", code_challenge: challenge, code_challenge_method: "S256", state, nonce, }).toString(); // verifier/state/nonce 应保存在服务端会话;不要写入 URL、日志或前端持久化存储。 return { authorizationUrl: url.toString(), verifier, state, nonce }; } export async function exchangeCode(code, verifier) { const response = await fetch(new URL("/v1/oauth/token", issuer), { method: "POST", headers: { "content-type": "application/json", authorization: `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`, }, body: JSON.stringify({ grant_type: "authorization_code", code, code_verifier: verifier, redirect_uri: redirectUri, client_id: clientId, }), }); const payload = await response.json(); if (!response.ok) throw new Error(payload.error_description ?? payload.error ?? "token exchange failed"); return payload; } ```