服务端 API Key 鉴权
创建服务端 Key,理解每次请求的鉴权、App/环境范围、权限、撤销与轮换。
SDK 0.0.1更新于
先分清三种身份
普通服务端 SDK 调用使用 gg_sk_ API Key。Key 固定到一个 App 和环境,Indiana 按权限验证每次请求。服务端读取或写入客户数据时,不需要先签发 Customer Token。
谁持有什么凭据| 调用方 | 凭据 | 用途 |
|---|
| 企业后端 | GROOVE_SECRET_KEY / gg_sk_ | 在 App/环境及权限内调用 Indiana |
| 企业客户端 | customerToken / gg_ct_ | 直接调用允许的客户接口;由企业后端签发 |
| Indiana 控制台成员 | Supabase Auth | 登录控制台、创建 App 和 Key |
谁持有什么凭据
企业后端
- 凭据
- GROOVE_SECRET_KEY / gg_sk_
- 用途
- 在 App/环境及权限内调用 Indiana
企业客户端
- 凭据
- customerToken / gg_ct_
- 用途
- 直接调用允许的客户接口;由企业后端签发
Indiana 控制台成员
- 凭据
- Supabase Auth
- 用途
- 登录控制台、创建 App 和 Key
Customer 是 Indiana 的业务记录,通过 App、环境和 externalUserId 关联你产品里的用户;它不共用控制台的账号密码。企业自己的登录系统负责确认用户身份,Indiana Key 不证明用户已登录。本文先完成只用 Key 的服务端接入,再说明客户端会话。
1. 创建 Sandbox 服务端 Key
- 使用对当前 App 有 apiKeys:write 权限的 Owner、Admin 或 Developer 登录,在 SDK 接入页选择 App 和 Sandbox。
- 创建 Key,仅勾选本次首次请求需要的 customers:write。Key 原文只显示一次,立即保存到自己的后端配置。
- 同页复制 base URL 和 App ID。base URL 填站点根地址,不追加 /api/v1。纯服务端请求无需配置浏览器来源。
Key 固定到一个 App/环境,有效期为 1–365 天。修改权限或到期时间时创建替代 Key;每个 App/环境最多保留 20 把有效 Key。
2. 安装、配置并完成首次请求
准备 Node.js 22 或更高版本。本指南固定使用已发布的 npm 0.0.1 beta,直接安装即可。
在应用根目录保存被 Git 忽略的 .env.local,替换下面的 App ID 和 Key;自建 Indiana 时使用自己的站点根地址。GROOVE_SECRET_KEY 只在后端读取,不加 NEXT_PUBLIC_ 前缀。
在同一目录新建 groove-server-check.mjs,完整复制下面代码。脚本会创建并保留一个 Sandbox 测试客户;不会签发客户 Token;若已配置默认套餐/自动试用政策,客户创建可能触发符合资格的分配。
首次请求完成
看到 requestId、Sandbox scope 和 customerId 表示 Indiana 已验证 Key 并完成数据库操作。重复运行仍返回同一 customerId,created 变为 false;这也是成功结果。初始化 SDK 没有网络校验,真正的鉴权发生在 API 请求时。
实际业务中,把固定测试 externalUserId 换成后端已经验证或业务任务已经授权的稳定用户 ID。不要把浏览器提交的 customerId 或用户 ID 直接当作授权。
1npm install @tinkerlandai/indiana-sdk@0.0.1
1# 从 SDK 接入页复制 Sandbox 配置;此文件须被 Git 忽略。2GROOVE_BASE_URL=https://groove-grow.vercel.app3GROOVE_PROJECT_ID=your-app-id4GROOVE_SECRET_KEY=your-sandbox-server-key
groove-server-check.mjsJavaScript 1import { createGrooveServer, GrooveError } from "@tinkerlandai/indiana-sdk/server";2 3class ConfigurationError extends Error {}4 5function required(name) {6 const value = process.env[name];7 if (!value) throw new ConfigurationError("Missing environment variable: " + name);8 return value;9}10 11try {12 const groove = createGrooveServer({13 baseUrl: required("GROOVE_BASE_URL"),14 apiKey: required("GROOVE_SECRET_KEY"),15 scope: {16 projectId: required("GROOVE_PROJECT_ID"),17 environment: "sandbox",18 },19 });20 21 // 固定的 Sandbox 测试身份;重跑返回同一 Customer。22 const { requestId, scope, customer, created } =23 await groove.getOrCreateCustomer({24 externalUserId: "docs-server-auth-check",25 });26 27 console.log(JSON.stringify({28 requestId, scope, customerId: customer.id, created,29 }, null, 2));30} catch (error) {31 if (error instanceof GrooveError) {32 // 只打印排查字段,不打印 Key、Token 或完整异常。33 console.error(JSON.stringify({34 code: error.code, status: error.status, requestId: error.requestId,35 }));36 } else if (error instanceof ConfigurationError) {37 console.error(error.message);38 } else {39 console.error("请求未完成。检查 Node 版本、环境文件和网络配置。");40 }41 process.exitCode = 1;42}
1node --env-file=.env.local groove-server-check.mjs
1{2 "requestId": "<request-id>",3 "scope": {4 "projectId": "<your-app-id>",5 "environment": "sandbox"6 },7 "customerId": "<customer-id>",8 "created": true9}
也可以直接通过 HTTP 调用
HTTP 与 SDK 使用相同 Key 和权限。先在终端环境中设置 GROOVE_BASE_URL、GROOVE_PROJECT_ID、GROOVE_SECRET_KEY,再执行以下命令;Node 的 --env-file 不会替终端导入变量。
成功返回 HTTP 200,JSON 包含 requestId、scope、created 和 customer。使用相同 App、环境和 externalUserId 重试返回原客户。
1curl -X POST "$GROOVE_BASE_URL/api/v1/customers" \2 -H "Authorization: Bearer $GROOVE_SECRET_KEY" \3 -H "X-Groove-Project: $GROOVE_PROJECT_ID" \4 -H "X-Groove-Environment: sandbox" \5 -H "Content-Type: application/json" \6 -d '{"externalUserId":"docs-server-auth-check"}'
3. 按需要增加权限
常用 SDK 方法与权限| SDK 方法 | HTTP | 凭据 | 权限 |
|---|
| getOrCreateCustomer | POST /api/v1/customers | 服务端 Key | customers:write |
| getCustomer | POST /api/v1/customers/get | 服务端 Key | customers:read |
| readHoldings | POST /api/v1/customer-holdings | 服务端 Key / 客户 Token | entitlements:read |
| mutateBalance | POST /api/v1/balances/execute | 服务端 Key | credits:consume |
| createCustomerSession | POST /api/v1/customer-sessions | 服务端 Key | sessions:create |
常用 SDK 方法与权限
getOrCreateCustomer
- HTTP
- POST /api/v1/customers
- 凭据
- 服务端 Key
- 权限
- customers:write
getCustomer
- HTTP
- POST /api/v1/customers/get
- 凭据
- 服务端 Key
- 权限
- customers:read
readHoldings
- HTTP
- POST /api/v1/customer-holdings
- 凭据
- 服务端 Key / 客户 Token
- 权限
- entitlements:read
mutateBalance
- HTTP
- POST /api/v1/balances/execute
- 凭据
- 服务端 Key
- 权限
- credits:consume
createCustomerSession
- HTTP
- POST /api/v1/customer-sessions
- 凭据
- 服务端 Key
- 权限
- sessions:create
SDK 的 scope 应与 Key 所属 App/环境相同。X-Groove-Project 和 X-Groove-Environment 只核对预期范围,不能通过修改它们切换到未授权 App。直接 HTTP 可以省略;传入时必须成对。
退款除 credits:consume 外还需 credits:manage;Funnel start 需要 sessions:create 和 funnels:run。其余附加条件查对应接口。
相关:完整接口与权限
可选:让客户端直接访问 Indiana
如果业务只在后端调用 Indiana,上面的流程已经足够。浏览器需要读取当前客户数据或展示 Funnel 时,才增加客户会话流程。
- 企业后端先用自己的登录系统确认用户,再映射该用户的 Customer。
- 为 Key 增加 sessions:create,以及客户端实际需要的权限,例如 entitlements:read。
- 后端调用 createCustomerSession(customer.id),只向已验证用户返回 customerToken / expiresAt,并设置 no-store。
- 客户端 identify(customerToken) 后可以直接请求 Indiana;浏览器来源须配置在当前 App/环境允许列表中。
Customer Token 默认 10 分钟有效,且不超过签发 Key 到期时间。只下放 entitlements:read、funnels:run、billing:read、billing:write、offers:resolve、credits:read;每次请求重新检查签发 Key 和权限。游客会话只获得 funnels:run。
当前没有 Public SDK Key 加任意用户 ID 来建立客户身份的入口。普通 SDK 接口拒绝带 Origin 的服务端 Key 请求,不能把 Secret Key 放进浏览器;Funnel 的独立网关来源规则见 Funnel 文档。
相关:完整后端会话接口与浏览器接入文件
相关:Funnel 运行凭据与流程
首次请求失败时怎么排查
常见失败与下一步| HTTP / code | 优先检查 |
|---|
| 401 / UNAUTHENTICATED | Key 是否完整复制、被部署正确读取,是否撤销或到期。服务端 Key 失败不能靠刷新 Customer Token 解决。 |
| 403 / FORBIDDEN | 首次请求需要 customers:write;普通服务端请求不要携带浏览器 Origin。 |
| 403 / SCOPE_MISMATCH | App ID、sandbox/live 是否与 Key 所属范围一致。 |
| 400 / INVALID_REQUEST | 输入是否符合 DTO;范围 Header 是否成对;body 是否为 JSON。 |
| 500 / INTERNAL_ERROR; 503 / SERVICE_UNAVAILABLE | 记录 code、status、requestId 排查 Indiana 依赖或网络问题;写入重试保留原幂等身份与输入。 |
| 501 / NOT_CONFIGURED | Indiana 必需服务未配置,由服务端维护者修复。 |
HTTP 错误为 { requestId, error: { code, message } }。SDK GrooveError 携带 code、status、requestId;SDK 不自动重试。上面的脚本只输出这些排查字段,不输出 Key 或 Token。
相关:完整错误说明
撤销和轮换 Key
- 纯服务端轮换:创建同 App/环境的新 Key 并授予所需权限 → 更新后端配置,确认请求成功 → 撤销旧 Key。
- 已接入客户端时,撤销旧 Key 前还要用新 Key 创建客户会话并更新客户端。renewCustomerSession 只接受原签发者,不能跨 Key 续签。
- 撤销或到期后,Key 及其签发的客户 Token 在后续鉴权失败;不会自动回滚已提交操作。
- SDK 不自动续签。客户 Token 到期时由宿主重新验证登录后签发或续签;grow.reset 只清内存,撤销会话须由后端显式调用 revokeCustomerSessions。
相关:客户会话生命周期与退出登录
附录:Indiana 怎样验证 Key
gg_sk_ 是通过 HTTPS 发送的随机 Bearer 凭据。创建时原文仅返回一次,数据库只保存 SHA-256 哈希;它不是 Supabase 用户 JWT,也不是每个请求正文的签名。
- 每次请求解析 Bearer,计算 SHA-256,通过 grow_authenticate_sdk 查凭据。
- 检查未撤销、未到期及 App/环境记录,生成可信身份,再验证接口权限、范围、来源和输入。
- 使用可信 scope 执行业务与对象归属校验,并验证响应。不跨请求缓存成功鉴权;验证失败不会执行对应业务。
附录:内部凭据与数据库权限
接入方只配置 GROOVE_SECRET_KEY。SUPABASE_SECRET_KEY 是 Indiana 自己的内部数据库凭据,接入方不需要持有;SDK 不提供数据库直连。Customer 存在 customers,Key 哈希存在 api_keys,会话哈希存在 customer_sessions。
控制台普通操作通过成员 Supabase Auth、当前成员权限和 RLS,普通写入使用 grow_console_writer。SDK 凭据网关及 scoped 业务 RPC 是单独记录的 service_role 例外,依靠可信 scope 和对象/业务校验,不能描述为受控制台成员 RLS 限制。
当前 Redis 请求限额策略为 observe,记录而不阻断;RATE_LIMITED 错误契约不能证明已经开启强制限流。
相关:服务端 SDK 方法参考