跳转至文档正文
灵途造物开发文档
开发文档 / 产品接入

LINGTU IDENTITY / DOCUMENTATION

接入登录、算力与支付

先完成应用登记与统一登录,再按需接入公共算力账务和统一支付。

GETTING STARTED

接入概览

灵途造物账号为旗下产品提供统一身份与授权登录。用户使用一个账号登录,你的产品通过 OpenID Connect(OIDC)验证身份,并建立自己的登录会话。

账号中心负责统一身份认证与公共算力账务。课程、会员、推广关系、企业角色与业务权限,由接入产品自行管理。

接入约定 当前支持
应用类型 管理员登记的自有可信 Web 产品(具有后端)
授权流程 Authorization Code + PKCE S256
客户端认证 client_secret_basic(后端 HTTP Basic)
授权范围 openid profile
身份签名 RS256,通过 JWKS 验证
外部身份唯一键 (issuer, sub)

浏览器只负责跳转和持有产品的会话 Cookie。client_secret、换码、令牌和验签均留在接入产品后端。当前不支持纯前端 SPA 直接换码、动态应用注册、密码授权或 Refresh Token。

01 / REGISTER

登记应用

请联系账号中心管理员,提供应用名称和完整回调地址。管理员在「接入应用」中登记后,向你交付 client_id 与 client_secret;密钥仅保存在产品后端的环境变量或密钥管理服务中。

回调地址逐字精确匹配,不支持通配符。生产环境必须使用 HTTPS;HTTP 回调仅允许在非生产环境使用 localhost 或 127.0.0.1。开发与生产分别登记应用。

# 配置在接入产品后端,禁止使用 NEXT_PUBLIC_ 前缀
OIDC_ISSUER=https://login.lingtuzaowu.com/oauth
OIDC_CLIENT_ID=YOUR_CLIENT_ID
OIDC_CLIENT_SECRET=YOUR_CLIENT_SECRET
APP_ORIGIN=https://your-product.example
# 登记的回调:https://your-product.example/auth/callback

应用资料编辑、客户端停用和密钥轮换会使既有授权失效,产品应要求用户重新登录。

DISCOVERY

服务地址

生产 Issuer 固定为 https://login.lingtuzaowu.com/oauth。在后端读取 Discovery,校验返回的 issuer 与配置完全相同,并校验端点来源,再使用其中的端点。

用途 方法 完整路径(域名均为 login.lingtuzaowu.com)
Discovery GET /oauth/.well-known/openid-configuration
发起授权 GET /oauth/authorize
兑换令牌 POST /oauth/token
用户资料 GET /oauth/userinfo
验签公钥 GET /oauth/jwks
撤销令牌 POST /oauth/revoke
账号状态 POST /api/client/account-status

注意账号状态接口位于站点根路径 /api/client/account-status,不在 /oauth 下。

02 / AUTHORIZE

发起授权

用户点击“使用灵途造物账号登录”后,先进入产品自己的后端登录路由,再由后端创建登录事务并跳转至授权端点。

  1. 用安全随机数生成 state、nonce 和 code_verifier,每次登录重新生成。
  2. 计算 code_challenge = BASE64URL(SHA256(code_verifier)),不带填充字符。
  3. 在后端保存事务,包含 state、nonce、verifier、浏览器绑定与站内 returnTo,十分钟过期。用 HttpOnly、SameSite=Lax 的 Cookie 绑定浏览器,生产环境加 Secure。
  4. 跳转至以下地址,所有参数通过 URLSearchParams 编码。
https://login.lingtuzaowu.com/oauth/authorize
  ?client_id=YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fyour-product.example%2Fauth%2Fcallback
  &response_type=code
  &scope=openid%20profile
  &state=RANDOM_STATE
  &nonce=RANDOM_NONCE
  &code_challenge=BASE64URL_SHA256_VERIFIER
  &code_challenge_method=S256

以上为分行展示,实际跳转使用单行 URL。returnTo 只允许产品站内路径,不要把任意外部地址作为登录后的跳转目标。用户已登录账号中心时,可以复用有效的中心登录。

03 / CALLBACK

授权成功后,浏览器返回登记的回调地址,带上 code 与 state;失败时可能返回 error。回调必须由产品后端处理。

步骤 1 · 检查浏览器绑定和事务有效期,要求 state 唯一且匹配。原子消费事务,防止重复回调,即使后续换码失败也不可复用。
步骤 2 · 处理 error,要求 code 唯一且存在,然后在后端兑换令牌。授权码有效期仅 60 秒,只能使用一次。

# 后端请求示意;变量来自服务端配置和当前登录事务
curl --request POST 'https://login.lingtuzaowu.com/oauth/token' \
  --user "$OIDC_CLIENT_ID:$OIDC_CLIENT_SECRET" \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode "code=$CODE" \
  --data-urlencode 'redirect_uri=https://your-product.example/auth/callback' \
  --data-urlencode "code_verifier=$CODE_VERIFIER"

client_secret_basic 的用户名、密码须按 OAuth 规则分别做表单编码后,再拼接为 client_id:client_secret 并 Base64 编码。当前颁发的客户端凭证使用 URL 安全字符;完整实现可参考下方 Node.js 示例。

步骤 3 · 使用 Discovery 的 jwks_uri 和成熟 JWT 库验证 id_token,限制算法为 RS256,校验固定 issuer、audience、sub、iat、exp、nonce 与整数 account_version;若存在 azp,必须为当前 client_id。仅解码 JWT 不等于验签。
步骤 4 · 使用 access_token 请求 userinfo,要求返回的 sub 和 account_version 与 ID Token 一致。

curl 'https://login.lingtuzaowu.com/oauth/userinfo' \
  --header "Authorization: Bearer $ACCESS_TOKEN"

步骤 5 · 按 (issuer, sub) 查找或创建产品本地用户,将 account_version 绑定本地会话,并在允许受保护访问前校验账号状态。登录成功后更换会话 ID,浏览器只接收产品的 HttpOnly 会话 Cookie。

不要按手机号、昵称自动关联已有用户。不要把令牌写入 localStorage、浏览器 Cookie 或 URL,也不要在日志中输出授权码、令牌或密钥。

CLAIMS & TOKENS

请求 openid profile 后,可获得以下身份资料。nickname 与 picture 可能缺省,产品应提供默认显示。

{
  "sub": "USER_SUBJECT",
  "nickname": "灵途用户",
  "picture": "https://example.com/avatar.png",
  "account_version": 1
}
字段 含义
sub 账号主体标识,与 issuer 组合为唯一外部身份
nickname 显示昵称,不用作唯一键
picture 头像 URL,可选
account_version 账号安全版本,绑定产品会话并持续比对
凭证 有效期 用途
授权码 60 秒 一次性兑换令牌
ID Token 5 分钟 验证本次登录身份
Access Token 10 分钟 仅用于 userinfo

资料不包含手机号、微信标识或产品权限。Access Token 不授予课程、会员或其他业务 API 的权限;产品会话寿命由产品管理,但必须执行下面的状态校验。

04 / SESSION

在产品的受保护访问中,由后端使用本应用的 Basic 凭证检查已获授权用户的状态。该接口只允许查询本应用已经获得授权的账号。

curl --request POST 'https://login.lingtuzaowu.com/api/client/account-status' \\
  --user "$OIDC_CLIENT_ID:$OIDC_CLIENT_SECRET" \\
  --header 'Content-Type: application/json' \\
  --data '{"sub":"USER_SUBJECT"}'
{ "active": true, "account_version": 1 }
结果 产品应如何处理
active=true,版本与会话一致 允许继续访问
active=false 或版本不一致 清除本地会话,要求重新登录
401 / 403(客户端凭证失效或被停用) 清除本地会话,检查应用配置
超时、中心不可用或其他失败 无有效缓存时返回 503,禁止沿用过期状态

可缓存成功状态,缓存最多 60 秒,从请求发起时计时。按应用与 sub 隔离缓存;流式连接也须在 60 秒内重新检查,并终止已撤销的连接。

产品普通退出只清除自己的会话,退出操作需防 CSRF。账号中心退出当前登录只清除中心会话;退出全部设备、禁用账号、密码修改和恢复会提升安全版本,使旧产品登录失效。不支持 OIDC RP-Initiated Logout;不要自行拼接 end_session_endpoint。

BUILD & VERIFY

项目提供独立的 Node.js 参考客户端 examples/oidc-client/server.mjs,包含 PKCE、一次性事务、后端换码、JWKS 验签、用户资料比对、状态查询与产品退出。

使用 Node.js 22 或更新版本。在非生产账号中心登记本地示例应用,回调地址为 http://localhost:4401/auth/callback,再运行:

cd examples/oidc-client
npm install
# 使用单独的开发账号中心,替换为实际开发 issuer
export OIDC_ISSUER=http://localhost:33887/oauth
export OIDC_CLIENT_ID=YOUR_DEV_CLIENT_ID
export OIDC_CLIENT_SECRET=YOUR_DEV_CLIENT_SECRET
export APP_ORIGIN=http://localhost:4401
export PORT=4401
npm start

打开 http://localhost:4401,点击登录。示例每次受保护访问均查询中心状态;用户、事务和会话保存在进程内存,重启即丢失,正式产品需改为持久化存储。示例需从项目源码运行,本页代码片段用于说明请求协议。

问题 检查方式
回调地址不匹配 比对登记地址、授权请求和换码请求,包括协议、端口、路径与尾部斜杠
invalid_client / 401 检查应用是否启用、Basic 编码和密钥是否已轮换
invalid_grant 授权码是否超时、重复使用,verifier 是否属于原登录事务
state / nonce 校验失败 Cookie 是否回传、事务是否过期,是否错误复用其他浏览器的事务
验签失败 固定 issuer、audience、RS256、JWKS 和服务器时钟是否正确
状态失效 清除会话并重新授权;确认账号和应用状态

上线前验证完整登录、重放回调、错误 state、密钥轮换、退出全部设备和中心不可用的处理。各产品的本地用户、业务数据与权限仍由各自产品维护。

API KEY / INTEGRATION

API Key 用于 Agent、插件或命令行访问子系统 API。用户统一在账号中心创建、选择授权应用和撤销;子系统负责校验身份及执行自己的业务权限检查。

1. 子系统开通接入

管理员登记应用,子系统后端完成校验接口接入后,在「接入应用 → 编辑应用」开启「允许 API Key 接入」(默认关闭)。沿用应用的 client_id / client_secret,仅保存在子系统后端。

2. 用户统一创建密钥

子系统可提供「管理 API Key」入口,跳转到账号中心 /account/api-keys。用户登录账号中心后填写名称、选择有效期并勾选允许访问的应用;默认 90 天,也可选择 30 天、365 天或长期。默认不授权任何应用,新应用不会自动加入授权。

明文仅创建时展示一次,由用户保存到工具的安全凭证配置。创建、编辑授权与撤销接口只接受账号中心登录会话;子系统不能用应用 Basic 凭证或用户 API Key 代用户创建密钥,也不应把中心管理接口当作自己的跨站创建接口。

3. 从创建到子系统使用:完整链路

假设子系统地址是 https://product.example,要开放自己的 GET /api/me。账号中心不会自动替子系统开放这个接口,也不会主动把用户密钥推送给子系统。

管理员:登记子系统应用 → 子系统实现校验 → 开启该应用 API Key 接入
用户:在子系统正常登录一次,建立本地用户与中央 issuer/sub 的关联
用户:到账号中心创建 API Key,勾选该子系统 → 将密钥保存到工具
工具:携带 Bearer API Key → 请求子系统 GET /api/me
子系统后端:携带自身 Basic 凭证 + 用户 API Key → 请求中心 verify
中心:核验密钥、有效期、账号状态及是否授权当前子系统 → 返回身份
子系统后端:按 issuer/sub 查本地用户 → 检查业务权限 → 返回业务结果

同一密钥可以授权多个子系统,各子系统使用自己的应用凭证校验。例如密钥只授权 A,B 即使拿到该密钥,用 B 的凭证校验也会得到 active=false。子系统无需同步或保存用户密钥库;只处理本次请求的密钥,保存本地身份关联和必要的 keyId 审计信息。

子系统开发者需要实现什么

  1. 在子系统设置页增加「管理 API Key」链接,指向 https://login.lingtuzaowu.com/account/api-keys;当前由用户自行返回子系统或工具完成配置,没有自动回传明文密钥的跳转协议。
  2. 为需要开放的业务 API 接入 API Key 认证中间件。现有浏览器 Cookie 登录不会自动使这些接口支持 API Key。
  3. 将中心返回的 (issuer, sub) 映射到 OIDC 登录时已建立的本地用户,复用自己的组织与业务权限检查;不要把 keyId 当用户 ID。
  4. 为每个开放接口定义允许的操作及输入,执行后在本地记录业务审计。涉及算力扣费时,继续使用既有算力接口与用户扣费授权。

用户 API Key 不放入子系统环境变量。子系统后端配置的是自己的应用凭证:

IDENTITY_ORIGIN=https://login.lingtuzaowu.com
OIDC_ISSUER=https://login.lingtuzaowu.com/oauth
OIDC_CLIENT_ID=当前子系统登记的应用ID
OIDC_CLIENT_SECRET=当前子系统自己的后端密钥

子系统后端接入示例

Node.js 22+ 可将 packages/api-key-sdk 作为独立包安装或发布到内部 npm,包名为 @lingtu/api-key-sdk;当前不假定它已经发布到公共 npm。以下为基于标准 Request/Response 的处理函数,接到子系统自己的 GET /api/me 路由即可。findLocalUser 是子系统需要提供的本地数据库适配函数,不是账号中心接口。

import { createApiKeyVerifier } from '@lingtu/api-key-sdk';

// 每个服务实例初始化一次;每次业务请求都调用 verify。
const verify = createApiKeyVerifier({
  identityOrigin: process.env.IDENTITY_ORIGIN,
  issuer: process.env.OIDC_ISSUER,
  clientId: process.env.OIDC_CLIENT_ID,
  clientSecret: process.env.OIDC_CLIENT_SECRET,
});

export async function getMe(request, { findLocalUser }) {
  const json = (status, body) => Response.json(body, {
    status, headers: { 'Cache-Control': 'no-store' },
  });
  const match = /^Bearer (ltak_[A-Za-z0-9_-]{43})$/i.exec(
    request.headers.get('authorization') || '',
  );
  if (!match) return json(401, { message: '请提供用户 API Key' });

  let identity;
  try {
    identity = await verify(match[1]);
  } catch {
    // 不记录或返回密钥、请求正文及底层异常。
    return json(503, { message: '身份校验暂时不可用' });
  }
  if (!identity) return json(401, { message: '密钥无效或未授权本应用' });

  const user = await findLocalUser(identity.issuer, identity.sub);
  if (!user || user.disabled) {
    return json(403, { message: '请先登录本产品关联账号,或联系管理员' });
  }
  // /api/me 只读取本人资料;组织/写入接口在此继续检查角色与数据归属。
  return json(200, { userId: user.id, nickname: user.nickname });
}

如果子系统使用其他语言,无需 SDK,也可以按下文协议直接调用中心 verify。中心校验只判断身份和当前应用访问资格,不执行子系统的业务请求。

用户在工具中怎么调用

将创建结果保存到工具的安全凭证配置,并把 API 地址设置为子系统的业务地址。下面的 AGENT_API_KEY 由安全环境提供,避免将明文粘贴到命令历史:

curl 'https://product.example/api/me' \
  --header "Authorization: Bearer $AGENT_API_KEY"

用户无需提供子系统的 client_secret,也无需先拿 API Key 换 OIDC Token。子系统收到请求后按上述流程校验;密钥管理页没有“填写子系统服务器密钥”的步骤。

接入完成后如何验收

  • 用户已关联本地账号,密钥授权当前子系统:/api/me 返回本地用户资料。
  • 密钥没勾选当前子系统、过期或撤销:业务接口返回 401,不执行业务。
  • 中心校验成功,但本地用户未关联、被禁用或缺少组织权限:返回 403。
  • 中心校验超时或不可用:返回 503,不沿用旧身份结果。
  • 同一密钥授权 A、B 后分别调用:身份 issuer/sub 相同,业务权限分别由 A、B 判断。

可运行的双应用版本见 examples/api-key-client;其中本地用户与组织是测试夹具,接入真实子系统时需替换为自己的数据库查询。

4. 校验协议与错误处理

工具请求子系统 API 时携带 Authorization: Bearer <API Key>。子系统每次业务请求都在后端调用账号中心 POST /api/client/api-keys/verify:以本应用凭证做 HTTP Basic 认证,请求 JSON 为 { "apiKey": "ltak_..." }。

校验成功返回 active、issuer、sub、keyId、expiresAt;以 (issuer, sub) 查找产品本地用户,再检查组织成员、角色和数据归属。未关联本地用户时,引导用户完成产品正常注册/登录流程;不能直接相信请求自报的用户或角色。

  • active=true:继续本地权限检查,通过后才执行操作。
  • active=false:返回 401,不执行操作。
  • 校验超时或不可用:返回 503,不使用旧结果继续执行。

不缓存校验结果,不记录密钥、Authorization 或请求正文。Node.js 22+ 可使用独立 SDK packages/api-key-sdk;双应用示例位于 examples/api-key-client。

5. 使用与撤销边界

API Key、OIDC 登录和算力扣费授权相互独立。密钥校验不创建产品账号、登录授权、扣费授权或会员权益;收费操作仍须核验已有扣费授权及额度。修改密码、退出全部设备不会撤销 API Key,用户需在账号中心单独撤销。

完整接口、SDK 示例、错误处理与生命周期见 API Key 接入契约。

CREDITS / SETUP

开通算力接入

公共算力接入按应用单独开通。只使用统一登录的产品无需配置;登录成功并不代表用户允许扣费。此处管理余额与账务,模型 API、任务执行和业务定价由产品自行实现。

  1. 管理员在「接入应用」登记登录应用,并完成 OIDC 对接。
  2. 在该应用行点击「算力接入」,选择所需权限并勾选「允许新资金操作」。保存后复用该应用现有的 client_id / client_secret,无需新增密钥。
  3. 产品后端沿用 OIDC_CLIENT_ID 与 OIDC_CLIENT_SECRET,以 HTTP Basic 调用算力接口。密钥禁止使用 NEXT_PUBLIC_ 前缀;ID Token、Access Token 与浏览器 Cookie 不能代替应用认证。
  4. 按业务选择直接消费或划拨,接入下方接口并联调用户授权、余额不足与超时重试。
配置项 说明
查询 query 查询用户余额和幂等操作结果;余额查询还要求用户已开通该产品扣费授权
消费 consume 直接扣费、冻结、结算、取消及续期
划拨 transfer 创建划拨意向、查单、补单与确认产品到账
到账通知地址(可选) 接收划拨通知;不填时主动轮询,填写后仍需定时补单
产品入口(可选) 用户从算力账户访问产品的入口

通知地址和产品入口须与该应用已登记的 OIDC 回调同源,生产环境使用 HTTPS,不跟随通知重定向。算力权限配置在应用的「算力接入」中完成,应用密钥统一在「编辑」中轮换;「算力账务」用于交易查询、对账和到账异常处理。

# 仅配置在接入产品后端,沿用现有登录配置
OIDC_CLIENT_ID=YOUR_CLIENT_ID
OIDC_CLIENT_SECRET=YOUR_CLIENT_SECRET
# 算力接口使用账号域名根地址,不带 /oauth
CREDITS_ORIGIN=https://login.lingtuzaowu.com

开发与生产分别开通,开发环境使用独立开发账号中心。应用密钥遗失可在「编辑」中轮换。轮换同时影响登录、算力接口和通知验签,旧密钥立即失效;产品同步更新 OIDC_CLIENT_SECRET,既有产品授权失效后须重新登录。关闭新资金操作不会删除原单据;处理未完成交易时应保留对应权限并使用当前有效应用密钥。

CREDITS / REQUIRED FLOW

产品页面必须先确认共享算力已开通,再查询余额与账单。不要用余额/流水接口的 403 来探测是否开通,也不要在页面挂载时无条件并发调用这两个接口。

“开通”分两层:管理员先为当前环境的应用配置启用的算力接入(灵途灵感使用 query、consume,不使用 transfer);用户再在账号中心选择不限额或每日额度并授权。统一登录成功不包含扣费授权。

页面进入时的调用顺序

  1. 产品后端校验本地 OIDC 会话及账号状态,从会话读取 sub。
  2. 后端用应用 Basic 调用 POST /api/credits/v1/product/grant-status,请求 {sub}。该接口需要 consume 权限和登录关联,但不要求用户已授权,也不返回余额。
  3. 若返回 {authorized:false,dailyLimit:null},页面进入“未开通”状态,仅展示「开通共享算力」及说明。此时不请求 balance、transactions 或 debit,不显示伪造的 0 余额,也不将它渲染为通用加载失败。
  4. 用户点击开通后,产品后端创建并保存随机 state(绑定当前会话、sub、10 分钟有效),调用 POST /api/credits/v1/product/grant-requests,发送 {sub,returnUri,state},再跳转到固定账号中心 origin + 返回的 confirmationPath。returnUri 必须是产品固定的回调路由,且与登记的 OIDC 回调同源。
  5. 用户在账号中心选择不限额或填写每日额度并明确确认;产品不能预填默认授权额度、静默授权或替用户确认。用户取消时保留“未开通”页面,不自动再次跳转。
  6. 返回产品后,后端校验并原子消费 state、检查有效期、当前会话与 sub,重新核验账号状态,再调用 grant-status。grant_result=authorized 只是回调提示,不能直接作为已授权证明。
  7. 只有中央明确返回 authorized:true,才加载余额与账单;此时两项查询可并发。进入页面、重新聚焦页面或授权返回后应重新确认状态,不能把已开通永久保存在浏览器里。
  8. 已开通后若查询/扣费返回 SPEND_NOT_AUTHORIZED(用户撤销或授权变化),清除展示中的旧余额/账单,回到“未开通”状态。不要自动重试扣费或静默恢复授权。资金操作仍沿用原幂等键及结果恢复规则。
校验登录 → grant-status
  authorized=false → 显示开通按钮 → 账号中心明确授权
                   → 产品回调校验 state → 再次 grant-status
  authorized=true  → 查询 balance + transactions → 展示余额/账单

区分应用配置与个人授权

中央结果 含义 产品应做什么
grant-status 200,authorized=false 当前用户尚未开通或已撤销 显示开通入口,停止余额和账单请求
PRODUCT_UNAVAILABLE / PRODUCT_DISABLED / SCOPE_REQUIRED 当前应用未开通、已停用或缺少权限 显示应用暂不可用/联系管理员;不能让用户在授权页里循环重试
INVALID_CREDENTIAL 应用凭证错误或失效 后端修复凭证;不能靠让用户反复登录解决
USER_NOT_BOUND 用户与当前应用没有有效登录关联 从产品重新发起 OIDC 登录后再检查
SPEND_NOT_AUTHORIZED 资金/查询接口发现用户授权已撤销或缺失 回到未开通状态;不伪造余额、不绕过授权
超时或 503 中央暂不可用,授权状态未知 展示可重试状态;不能当成已授权或未经确认的新扣费

给产品实现 AI 的改动清单

  • 产品后端提供本产品的授权状态查询、开通发起及固定授权回调路由;中央真实接口为上面的 grant-status、grant-requests,不要臆造中央 /enable 或 /activate 接口。
  • 前端明确区分 checking(检查中)、needs-authorization(未开通)、ready(可查询)及 unavailable(不可用);只有 ready 才启用余额和账单查询,包括数据请求库的 enabled 条件。
  • 浏览器只请求本产品后端,不持有 client_secret,也不直接携带用户 Cookie 调用中央用户授权写接口。用户每天的扣费额度只在账号中心确认。
  • 保留授权取消、回调重放/过期、撤销授权、应用停用、密钥轮换及中央暂不可用的处理。不能为了消除控制台 403 放宽中央权限检查。
  • 本地联调的浏览器授权、discovery、换码、JWKS、userinfo、账号状态及算力请求必须落在同一个开发账号中心。Whistle 只改变浏览器路由;产品后端需配置自身的开发通道。开发凭证用 .env.development.local,不得使用生产 client_id 去本地账号中心查询。

验收场景

  1. 新用户未开通:只发生状态检查,余额/账单请求次数均为 0,显示开通按钮。
  2. 用户取消开通:仍不查询余额和账单,不自动再次跳转。
  3. 授权成功:回调校验 state 并重新确认 authorized=true 后,两项查询正常返回。
  4. 用户撤销:下一次状态检查或 SPEND_NOT_AUTHORIZED 使页面返回未开通,并清除旧资金展示。
  5. 应用未配置/权限不足/凭证错误:显示管理员可定位的错误,不能伪装成用户未授权或无限跳转。
  6. state 错误、重复、过期或账号切换:拒绝回调,不查询资金数据;仅有 grant_result=authorized 也不能通过。

CREDITS / CONSUME

1 元 = 100 算力,1 算力 = 10 个最小单位。接口金额使用整数十进制字符串,例如 "1000" 表示 100 算力(1 元),不接受小数、负数、前导零或科学计数法。单笔最大 9,000,000,000,000,000 最小单位。

所有产品接口位于账号域名的 /api/credits/v1/product 下,复用 OIDC 的 client_secret_basic,使用 Authorization: Basic BASE64(FORM_ENCODE(client_id) + ":" + FORM_ENCODE(client_secret))。sub 必须取自已验签且账号状态有效的产品后端会话,不能相信浏览器提交的付款人。

直接消费:用户先登录该产品,通过「开通共享算力」跳转到账号中心确认授权并设置每日额度,也可在「算力流水」管理授权。确定价格可直接扣费;任务成本不确定时先冻结,执行后结算实际金额,差额释放。中央统一管理余额,产品无需维护同一份余额副本。

方法与相对路径 权限 请求或用途
POST /balance query {sub},查询已授权用户余额
POST /debits consume {sub,amount},直接扣费
POST /holds consume {sub,amount},冻结 30 分钟
GET /holds/:id consume 查询当前冻结状态
POST /holds/:id/extend consume {amount},追加冻结
POST /holds/:id/renew consume {},续 30 分钟,最迟到创建后 24 小时
POST /holds/:id/capture consume {amount},结算不超过冻结总额,可为 "0"
POST /holds/:id/cancel consume {},释放全部冻结
GET /operations?operation=debit&key=... query 按操作与幂等键查询历史成功结果

所有金额写操作须提供 Idempotency-Key。同一业务操作始终复用同一键与参数;网络超时后按原键查询或重试,不能换键再次扣费。同键不同参数返回 IDEMPOTENCY_CONFLICT。键为 1–128 位 ASCII 字母、数字及 _ : . / -,区分大小写。

// 仅在产品后端执行;sub 来自有效会话,businessOrderId 持久保存
const formEncode = (value) => new URLSearchParams({ v: value }).toString().slice(2);
const basic = Buffer.from(`${formEncode(process.env.OIDC_CLIENT_ID)}:${formEncode(process.env.OIDC_CLIENT_SECRET)}`).toString('base64');
const response = await fetch(`${process.env.CREDITS_ORIGIN}/api/credits/v1/product/debits`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Basic ${basic}`,
    'Idempotency-Key': `debit:${businessOrderId}`,
  },
  body: JSON.stringify({ sub: authenticatedSession.sub, amount: '1000' }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.message);

每日额度按 Asia/Shanghai 计算,消费与冻结占用额度;差额、取消与过期释放原冻结日额度。产品必须在冻结有效期内执行任务,提前续期并在 24 小时内结束。过期冻结不能结算。撤销扣费授权或停用账号、产品后不能新增支出,已有冻结仍可按保留的权限结算或取消。

常见错误包括 SPEND_NOT_AUTHORIZED(未授权)、DAILY_LIMIT(每日额度不足)、INSUFFICIENT_BALANCE(余额不足)、USER_NOT_BOUND(未登录关联)、INVALID_CREDENTIAL(应用密钥失效)、SCOPE_REQUIRED(缺少权限)和 HOLD_EXPIRED(冻结到期)。错误返回 {code,message};503 或网络超时属于结果可能不确定的情况,保留原幂等键恢复。

CREDITS / TRANSFER

划拨到产品:适合产品自有个人或组织钱包。产品后端先核验当地接收权限,创建划拨意向,再将用户跳转到账号中心返回的 confirmationPath。用户逐笔确认后中央扣款,产品查单核实后在本地入账。这一路径使用逐笔确认,不以直接扣费授权代替确认。

方法与相对路径(前缀 /api/credits/v1/product) 输入或用途
POST /transfers {sub,amount,recipientType,recipientId,recipientName},须 transfer 权限与幂等键
GET /transfers/:id 查询本产品划拨单,只有 state=committed 才能入账
GET /transfers/pending 待到账单,30 条一页,cursor 翻页
POST /transfers/:id/ack 本地事务提交后确认到账,重复调用安全,无需额外幂等键

recipientType 为 person 或 organization。创建后付款人、金额及接收方不可更改。产品只向浏览器提供账号中心跳转地址,不向浏览器暴露应用密钥。

到账通知正文为 {eventId,type:"credits.transfer.committed",transferId},请求头包含 x-credits-timestamp(Unix 秒)和 x-credits-signature。产品校验时间误差不超过 5 分钟,以恒定时间比较验签:签名为 HMAC-SHA256(hex(SHA256(secret)), timestamp + "." + rawBody) 的十六进制字符串;secret 为该应用当前的 client_secret。通知只提供线索,产品仍须携带应用 Basic 认证主动查单,核对产品、金额、接收对象与 committed 状态。

产品本地必须将唯一 inbox 记录与余额增加放在同一个数据库事务内,提交后再 ACK。不能先 ACK 再入账;入账成功但 ACK 丢失时,根据 inbox 去重并补发 ACK。HTTP 2xx 不代表已到账。即使配置通知,也须定时拉取 pending 补单。

中央已扣款、产品待到账时不会自动恢复余额。管理员在「算力账务 → 到账异常」核验并重试通知,不能直接标记到账。当前划拨是单向操作,不支持退回、提现、现金退款或用户间转赠。产品补贴、费用与组织关系由产品管理;中央不展示产品当地余额。

参考仓库中的 examples/credits-client,包含独立 MySQL inbox、接收权限检查、验签和补单。产品无需连接中央数据库。当前运行时未安装真实支付适配器,在线充值默认关闭;后台手工算力调整不代表真实支付。

PAYMENTS

产品后端复用应用凭证接入统一支付;各自有产品按配置商户收款,公共算力固定使用中央商户。会员价格、期限和权益仍由接入产品管理。

支持电脑扫码、微信内网页和微信外手机浏览器,会员采用逐次购买或续费。后台在「支付接入」配置主体、场景及应用权限,未验证渠道保持关闭。

商品支付支持在产品内确定金额,由产品后端下单,再跳转收银台直接付款。公共算力充值目前需在账号中心选择金额;产品后端直接创建算力充值单的接口尚未提供。

完整接口、通知验签和商户配置见 统一支付接入文档。Node.js 22+ 接入可直接使用以下公开文件:

可将上述文档链接提供给有联网能力的 AI 读取,或下载 SDK 安装到产品后端。