面向 LLM / Vibe Coding 的接入指南
这份文档不是后台使用说明,而是给“其他项目接入柚卡”时使用的工程指南,目标是让 LLM、vibe coding 工具和工程师都能直接据此生成接入代码。
1. 项目背景与定位
柚卡(yuzuca)是一个面向第三方应用接入的授权与订阅管理系统,核心作用是把原本散落在业务系统内的这些能力统一收口:
- 项目级隔离
- 客户管理
- 套餐与订阅管理
- 功能权限判断
- 额度计量与扣量
- 卡密兑换
- API Key 鉴权
- 客户端登录态管理
从接入方视角,可以把柚卡理解为“授权中台”或“订阅与额度中心”。业务系统负责自己的用户和业务数据,柚卡负责授权、套餐、额度、卡密和登录令牌。
2. 柚卡负责什么,不负责什么
柚卡负责:
- 判断某个客户是否有某个功能
- 判断某个客户剩余额度是否足够
- 扣减、返还或直接设置某个计量功能的用量
- 为客户开通/延长套餐
- 处理卡密兑换
- 提供客户端登录和令牌刷新能力
接入方通常仍负责:
- 自己的业务用户体系
- 业务用户与柚卡客户的映射关系
- 什么时候触发权限校验
- 什么时候触发额度扣减
- 密钥保管、轮换与审计
- 业务失败后的补偿逻辑
3. 核心模型
建议先记住这几个对象:
Project:最外层隔离单元。通常对应一个应用、一个环境或一个租户。Customer:授权主体,内部主记录保留username作为显示名和默认登录名。CustomerIdentifier:客户标识。用于承载外部系统用户 ID、邮箱、卡号、用户名等多个可定位标识。Feature:功能标识,调用时使用稳定slug,例如chat-basic、image-generate。Plan:套餐,表示一组功能与额度组合。PlanQuota:套餐内某个功能的额度配置。Subscription:某客户在某段时间内拥有某套餐。SubscriptionUsage:某订阅下某功能的当前用量快照。LicenseCode:卡密,兑换后为客户创建或延长订阅。ProjectAPIKey:项目 API Key,分PRIVATE和PUBLIC两类。
几个关键事实:
- 项目之间数据隔离。
- 同项目内同一个
(type,value)只能绑定到一个客户。 Customer.username仍然存在,但外部接入应优先使用{ type, value }标识对象,而不是把用户名当资源主键。- 第三方调用时,项目通常由 API Key 自动确定,不需要额外传
projectId。 - 功能调用应始终使用
feature.slug,套餐调用应始终使用plan.slug。
4. 接入方式怎么选
4.1 服务端接入
调用代码运行在你自己的服务端,并且可以安全保存密钥时,使用 Private API 或 YuzucaPrivateClient。
适合:
- 管理任意客户
- 创建/更新/删除客户
- 为任意客户开通套餐
- 扣减任意客户额度
- 后端业务网关统一判断权限
4.2 客户端接入
调用代码运行在浏览器、移动端、桌面客户端等公开环境时,使用 Public Client API 或 YuzucaPublicClient。
适合:
- 客户登录后查询自己的资料、订阅、额度、卡密
- 客户自己兑换卡密
- 客户端判断“当前登录用户自己是否有权限”
4.3 绝对不要混用
- 不要把
PRIVATEAPI Key 放到前端、客户端或公开环境。 - 不要用 Public API 管理其他客户。
- 不要让 LLM 生成依赖
/api/admin/*、/api/portal/*的外部接入代码,这些不是面向第三方项目的标准接入面。
5. 最小接入流程
5.1 服务端最小流程
- 在柚卡后台创建项目、功能、套餐、API Key。
- 在业务系统内保存
PRIVATEAPI Key。 - 业务用户首次接入时,建立“业务用户 -> yuzuca customer identifier”映射。
- 需要授权判断时调用
POST /api/private/v1/entitlements。 - 业务成功后调用
POST /api/private/v1/usage扣量。 - 需要直接开通套餐时调用
POST /api/private/v1/subscriptions/activate。 - 需要卡密激活时调用
POST /api/private/v1/codes/redeem。
5.2 客户端最小流程
- 在柚卡后台创建
PUBLICAPI Key。 - 如果该 Key 启用了
requireSignature,客户端必须附带签名头。 - 客户使用标识对象和密码调用
POST /api/public/v1/auth/login登录。 - 保存
accessToken和refreshToken。 - 客户端读取本人信息用
GET /api/public/v1/me。 - 权限检查用
POST /api/public/v1/me/entitlements/check。 - 卡密兑换用
POST /api/public/v1/me/redemptions。 - 访问令牌过期后调用
POST /api/public/v1/auth/refresh。
6. 通用协议约定
所有接口都返回统一结构:
{ "ok": true, "requestId": "req_xxx", "data": {} }
错误结构:
{ "ok": false, "requestId": "req_xxx", "error": { "code": "INVALID_REQUEST", "message": "...", "details": [] } }
通用约定:
- 响应头里会返回
X-Request-Id,排错时应记录下来。 - 写接口建议始终传
Idempotency-Key,尤其是扣量、兑换、激活这类会重复重试的操作。 - Public API 始终需要
X-Yuzuca-Key。 - Public API 的登录态接口还需要
Authorization: Bearer <ACCESS_TOKEN>。
7. HTTP 接口清单
7.1 Private API
基址:/api/private/v1
鉴权:
Authorization: Bearer <PRIVATE_API_KEY>
| 方法 | 路径 | 作用 |
|---|---|---|
GET | /api/private/v1/info | 读取当前项目信息与端点清单 |
POST | /api/private/v1/entitlements | 批量校验任意客户的功能权限与额度 |
POST | /api/private/v1/usage | 扣减、返还、设置用量 |
POST | /api/private/v1/redemptions | 为任意客户兑换卡密 |
GET | /api/private/v1/customers | 分页查询客户 |
POST | /api/private/v1/customers | 创建客户 |
POST | /api/private/v1/customers/resolve | 按标识解析客户并拿到 customerId |
GET | /api/private/v1/customers/:customerId | 查询客户详情 |
PATCH | /api/private/v1/customers/:customerId | 更新客户 |
DELETE | /api/private/v1/customers/:customerId | 删除客户 |
GET | /api/private/v1/plans | 分页查询套餐 |
GET | /api/private/v1/subscriptions | 分页查询订阅 |
GET | /api/private/v1/subscriptions/:subscriptionId | 查询订阅详情 |
DELETE | /api/private/v1/subscriptions/:subscriptionId | 删除订阅 |
POST | /api/private/v1/subscriptions/activate | 直接开通或延长套餐 |
POST | /api/private/v1/codes/redeem | 兑换卡密 |
最常用请求体:
{ "customer": { "type": "external_user_id", "value": "u_1001" }, "features": [{ "feature": "image-generate", "required": 2 }] }
{ "customer": { "type": "external_user_id", "value": "u_1001" }, "feature": "image-generate", "action": "deduct", "amount": 1 }
{ "customer": { "type": "email", "value": "user@example.com" }, "plan": "pro", "duration": "P30D" }
{ "code": "ABC-DEF-GHI", "customer": { "type": "external_user_id", "value": "u_1001" }, "autoCreate": false }
7.2 Public Client API
基址:/api/public/v1
公共鉴权:
X-Yuzuca-Key: <PUBLIC_API_KEY>
登录态接口额外要求:
Authorization: Bearer <ACCESS_TOKEN>
当 PUBLIC API Key 开启强制签名时,还必须携带:
X-Yuzuca-Timestamp: <unix-seconds>
X-Yuzuca-Nonce: <random>
X-Yuzuca-Signature: <hmac-signature>
| 方法 | 路径 | 作用 |
|---|---|---|
GET | /api/public/v1/info | 读取项目信息与公开端点 |
POST | /api/public/v1/auth/login | 客户登录 |
POST | /api/public/v1/auth/refresh | 刷新令牌 |
POST | /api/public/v1/auth/logout | 登出 |
GET | /api/public/v1/me | 读取当前客户资料 |
GET | /api/public/v1/me/subscriptions | 查询自己的订阅列表 |
GET | /api/public/v1/me/subscriptions/:subscriptionId | 查询自己的订阅详情 |
GET | /api/public/v1/me/subscriptions/:subscriptionId/usages | 查询订阅下各功能用量 |
GET | /api/public/v1/me/subscriptions/:subscriptionId/usages/:featureId/history | 查询某功能历史用量快照 |
GET | /api/public/v1/me/license-codes | 查询自己的卡密记录 |
POST | /api/public/v1/me/redemptions | 为自己兑换卡密 |
POST | /api/public/v1/me/entitlements/check | 检查自己是否有功能权限 |
最常用请求体:
{ "identifier": { "type": "email", "value": "user@example.com" }, "password": "secret" }
{ "features": [{ "feature": "chat-basic" }, { "feature": "image-generate", "required": 2 }] }
{ "code": "ABC-DEF-GHI" }
8. SDK 接口清单
安装:
pnpm add @yuzuca/sdk
导出项:
YuzucaPrivateClientYuzucaPublicClientcreateHmacSignatureProvidercreateIdempotencyKeyYuzucaError- 各类请求/响应 TypeScript 类型
8.1 YuzucaPrivateClient
import { YuzucaPrivateClient } from "@yuzuca/sdk";
const client = new YuzucaPrivateClient({
apiKey: process.env.YUZUCA_PRIVATE_KEY!,
baseUrl: "https://your-yuzuca.example.com",
});
方法清单:
| 分组 | 方法 | 对应 HTTP |
|---|---|---|
info | get() | GET /info |
entitlements | check(input) | POST /entitlements |
entitlements | checkOne({ customer, feature, required }) | POST /entitlements |
usage | mutate(input) | POST /usage |
usage | deduct(input) | POST /usage |
usage | add(input) | POST /usage |
usage | setUsed(input) | POST /usage |
usage | setRemaining(input) | POST /usage |
redemptions | redeem(input) | POST /redemptions |
customers | list(query?) | GET /customers |
customers | create(input) | POST /customers |
customers | resolve(identifier) | POST /customers/resolve |
customers | get(customerId) | GET /customers/:customerId |
customers | update(customerId,input) | PATCH /customers/:customerId |
customers | delete(customerId) | DELETE /customers/:customerId |
plans | list(query?) | GET /plans |
subscriptions | list(query?) | GET /subscriptions |
subscriptions | get(subscriptionId) | GET /subscriptions/:subscriptionId |
subscriptions | delete(subscriptionId) | DELETE /subscriptions/:subscriptionId |
subscriptions | activate(input) | POST /subscriptions/activate |
codes | redeem(input) | POST /codes/redeem |
| 实例方法 | withApiKey(apiKey) | 切换服务端密钥 |
8.2 YuzucaPublicClient
import { YuzucaPublicClient, createHmacSignatureProvider } from "@yuzuca/sdk";
const client = new YuzucaPublicClient({
apiKey: process.env.YUZUCA_PUBLIC_KEY!,
baseUrl: "https://your-yuzuca.example.com",
signatureProvider: createHmacSignatureProvider(process.env.YUZUCA_SIGNING_SECRET!),
});
方法清单:
| 分组 | 方法 | 对应 HTTP |
|---|---|---|
info | get() | GET /info |
me | get() | GET /me |
subscriptions | list() | GET /me/subscriptions |
subscriptions | get(subscriptionId) | GET /me/subscriptions/:subscriptionId |
subscriptions | getUsages(subscriptionId) | GET /me/subscriptions/:subscriptionId/usages |
subscriptions | getUsageHistory(subscriptionId,featureId) | GET /me/subscriptions/:subscriptionId/usages/:featureId/history |
licenseCodes | list() | GET /me/license-codes |
redemptions | redeem(input) | POST /me/redemptions |
entitlements | check(input) | POST /me/entitlements/check |
entitlements | checkOne({ feature, required }) | POST /me/entitlements/check |
| 实例方法 | login(identifier,password) | POST /auth/login |
| 实例方法 | refresh(refreshToken?) | POST /auth/refresh |
| 实例方法 | logout(refreshToken?) | POST /auth/logout |
| 实例方法 | getTokens() | 读取本地 token |
| 实例方法 | setTokens(tokens) | 写入本地 token |
| 实例方法 | clearTokens() | 清空本地 token |
| 实例方法 | setSignatureProvider(provider) | 动态设置签名器 |
9. 推荐给 LLM 的接入规则
如果你要让 LLM 为其他项目生成接入代码,建议把下面这些规则直接放进上下文:
- 这是一个授权/订阅/额度中心,不是业务用户中心。
- 外部项目接入时优先使用
@yuzuca/sdk,只有在非 TypeScript 或需要极致控制时才直接调 HTTP。 - 服务端环境只能使用
PRIVATEAPI Key,对应/api/private/v1/*。 - 浏览器、App、桌面客户端只能使用
PUBLICAPI Key,对应/api/public/v1/*。 - 不要为外部接入生成
/api/admin/*、/api/portal/*的调用代码。 customer一律使用稳定标识对象{ type, value },不要把昵称或展示名当标识。- 客户详情管理接口使用
customerId路径参数,不再使用username路由。 feature和plan一律使用slug,不要用展示名称。- 对任何写请求都优先生成
Idempotency-Key。 - 扣量应在业务成功后触发,权限检查应在业务执行前触发。
- 如果 Public API Key 启用了签名,必须附加
X-Yuzuca-Timestamp、X-Yuzuca-Nonce、X-Yuzuca-Signature。 - Public API 登录后要保存
accessToken和refreshToken,过期后用refresh换新。
10. 最小示例
10.1 服务端校验权限并扣量
import { YuzucaPrivateClient, createIdempotencyKey } from "@yuzuca/sdk";
const client = new YuzucaPrivateClient({
apiKey: process.env.YUZUCA_PRIVATE_KEY!,
baseUrl: process.env.YUZUCA_BASE_URL!,
});
const customer = { type: "external_user_id", value: "user_1001" };
const entitlement = await client.entitlements.checkOne({
customer,
feature: "image-generate",
required: 1,
});
if (!entitlement.allowed) throw new Error("Quota not enough");
await client.usage.deduct(
{ customer, feature: "image-generate", amount: 1 },
{ idempotencyKey: createIdempotencyKey("img_gen") },
);
10.2 客户端登录并查询自己的订阅
import { YuzucaPublicClient } from "@yuzuca/sdk";
const client = new YuzucaPublicClient({
apiKey: process.env.NEXT_PUBLIC_YUZUCA_KEY!,
baseUrl: process.env.NEXT_PUBLIC_YUZUCA_BASE_URL!,
});
await client.login({ type: "email", value: "user@example.com" }, "secret");
const me = await client.me.get();
const subscriptions = await client.subscriptions.list();
11. 接入建议总结
最推荐的接入姿势是:
- 业务服务端统一走
YuzucaPrivateClient - 公开客户端统一走
YuzucaPublicClient - 把客户标识对象、功能
slug、套餐slug当成稳定契约 - 先做“鉴权检查 + 扣量”两条链路,再补“卡密兑换”和“客户自助中心”
如果只是要让另一个项目快速接入,最先实现的通常只有 4 个动作:
- 识别客户标识
- 检查功能权限
- 扣减额度
- 查询或兑换订阅/卡密