SDK 指南
Profy 提供 TypeScript 和 Python 两套官方 SDK,封装了 OAuth token 交换、自动刷新、Events API 上报等核心能力。
npm install @profy-ai/sdk
# or
bun add @profy-ai/sdk
pip install profy-sdk
# or
pip install profy-sdk[sqlalchemy] # 含可选的 SQLAlchemy 存储层
初始化
import { ProfyApp } from "@profy-ai/sdk";
const profy = new ProfyApp({
clientId: process.env.PROFY_APP_ID!,
clientSecret: process.env.PROFY_APP_SECRET!,
baseUrl: "https://profy.cn", // 可选,默认 https://profy.cn
onTokenRefresh: (token) => { // 可选,token 刷新后的回调
// 持久化新 token(存 DB / Redis / 文件)
},
});
from profy import ProfyApp
profy = ProfyApp(
client_id="your-app-id",
client_secret="your-app-secret",
base_url="https://profy.cn", # 可选
on_token_refresh=lambda token: ..., # 可选,token 刷新后持久化
)
from profy import ProfyAppSync
profy = ProfyAppSync(
client_id="your-app-id",
client_secret="your-app-secret",
)
核心 API
1. 授权码换 Token
用户在 Profy 授权页面同意后,回调地址收到 code,在后端调 SDK 换取 Token:
const token = await profy.exchangeCode(code, redirectUri);
// token: { accessToken, refreshToken, userId, expiresAt }
token = await profy.exchange_code(code, redirect_uri)
# token: TokenData(access_token, refresh_token, user_id, expires_at)
授权码交换必须在后端完成。App Secret 不能暴露给前端。
2. 刷新 Token
Token 过期后用 Refresh Token 续期。reportEvent 内部自动处理刷新,也可手动调用:
const newToken = await profy.refreshToken(token.refreshToken);
new_token = await profy.refresh_token(token.refresh_token)
3. 上报计费事件
用户触发付费操作时,调用 SDK 上报事件。SDK 会自动处理 token 过期重试:
const result = await profy.reportEvent("generate_report", {
token,
idempotencyKey: "unique-key-123", // 可选,幂等键
metadata: { source: "web" }, // 可选,附加信息
});
result = await profy.report_event(
"generate_report",
token=token,
idempotency_key="unique-key-123", # 可选
metadata={"source": "web"}, # 可选
)
4. Token 过期检测
import { isTokenExpired } from "@profy-ai/sdk";
if (isTokenExpired(token)) {
// token 即将过期(30 秒 buffer)
}
if token.is_expired:
# token 即将过期(30 秒 buffer)
pass
Token 持久化(Contrib 存储层)
SDK 核心不依赖任何 ORM/数据库。Token 怎么存由你决定。
如果你使用 Drizzle (TS) 或 SQLAlchemy (Python),SDK 提供了可选的 contrib 模块,开箱即用。不想用 contrib 也完全可以自建。
方案 A:使用 SDK Contrib(推荐快速接入)
import { ProfyApp } from "@profy-ai/sdk";
import { profyOAuthToken, DrizzleTokenStore } from "@profy-ai/sdk/contrib/drizzle";
// 1. 将 profyOAuthToken 加入你的 Drizzle schema
// 然后执行 drizzle-kit generate && drizzle-kit migrate 建表
// 2. 创建 Store
const store = new DrizzleTokenStore(db);
// 3. 初始化 SDK,绑定 store
const profy = new ProfyApp({
clientId: "...",
clientSecret: "...",
onTokenRefresh: (token) => store.save(token),
});
// 4. 授权后保存 token
const token = await profy.exchangeCode(code, redirectUri);
await store.save(token, "events:write");
// 5. 下次使用时加载 token
const savedToken = await store.load(userId);
from profy import ProfyApp
from profy.contrib.sqlalchemy import create_token_model, SQLAlchemyTokenStore
# 1. 创建 ORM model(跟随 Base.metadata.create_all() 自动建表)
ProfyOAuthToken = create_token_model(Base)
# 2. 创建 Store
store = SQLAlchemyTokenStore(async_session_factory, ProfyOAuthToken)
# 3. 初始化 SDK,绑定 store
profy = ProfyApp(
client_id="...",
client_secret="...",
on_token_refresh=store.on_refresh,
)
# 4. 授权后保存 token
token = await profy.exchange_code(code, redirect_uri)
await store.save(token.user_id, token, scope="events:write")
# 5. 下次使用时加载 token
saved_token = await store.load(user_id)
方案 B:自建存储
SDK 只需要一个 onTokenRefresh 回调函数,不关心你用什么数据库:
import { ProfyApp, type TokenData } from "@profy-ai/sdk";
// 用 Redis / Prisma / TypeORM / 文件 / 任何方式存储
async function saveToken(token: TokenData) {
await redis.set(`profy:token:${token.userId}`, JSON.stringify(token));
}
const profy = new ProfyApp({
clientId: "...",
clientSecret: "...",
onTokenRefresh: saveToken,
});
from profy import ProfyApp, TokenData
# 用 Redis / MongoDB / 文件 / 任何方式存储
async def save_token(token: TokenData):
await redis.set(f"profy:token:{token.user_id}", token.access_token)
profy = ProfyApp(
client_id="...",
client_secret="...",
on_token_refresh=save_token,
)
Contrib 自动建表说明
| ORM | 建表方式 | 时机 |
|---|
| SQLAlchemy | Base.metadata.create_all(engine) | 应用启动时自动创建(如果表不存在) |
| Drizzle | drizzle-kit generate + drizzle-kit migrate | 手动执行迁移命令 |
SQLAlchemy 的 create_all 只能建新表,不能给已有表加列。如果你升级了 SDK 且 contrib model 有新字段,需要手动 ALTER TABLE 或使用 Alembic 迁移。
Contrib 表结构
profyOAuthToken / profy_oauth_token 表默认字段:
| 字段 | 类型 | 说明 |
|---|
id | serial / integer | 主键 |
profy_user_id | string(64) | Profy 用户 ID(唯一索引) |
access_token | text | JWT Access Token (1h) |
refresh_token | text | Refresh Token (90d) |
expires_at | timestamp | Access Token 过期时间 |
scope | string | 授权 scope |
created_at | timestamp | 创建时间 |
updated_at | timestamp | 更新时间 |
表名可自定义:
ProfyOAuthToken = create_token_model(Base, table_name="my_profy_tokens")
错误处理
SDK 提供结构化异常,方便业务层精确处理:
| 异常 | HTTP 状态码 | 含义 |
|---|
AuthExpired | 401 | Token 过期且 refresh 也失败,需要用户重新授权 |
InsufficientBalance | 402 | 用户余额不足 |
InvalidEvent | 400 | 事件名未在 Profy Studio 配置 |
ProfyApiError | 其他 | 通用 API 错误(含 429 限频) |
import { AuthExpired, InsufficientBalance } from "@profy-ai/sdk";
try {
await profy.reportEvent("action", { token });
} catch (e) {
if (e instanceof AuthExpired) {
// 引导用户重新授权
} else if (e instanceof InsufficientBalance) {
// 提示用户充值
}
}
from profy import AuthExpired, InsufficientBalance
try:
await profy.report_event("action", token=token)
except AuthExpired:
# 引导用户重新授权
except InsufficientBalance:
# 提示用户充值
SDK vs 自建对照
| 维度 | 用 SDK | 自建 |
|---|
| Token Exchange | profy.exchangeCode() | 自己调 POST /oauth/token |
| Token Refresh | SDK 自动续期 | 自己检测过期 + 手动 refresh |
| 重试 / 幂等 | SDK 内置 401 重试 | 自己实现 |
| 存储 | contrib 或 自定义回调 | 完全自定义 |
| 计费上报 | profy.reportEvent() | 自己调 POST /openapi/v1/events |
| 错误分类 | 结构化异常 | 自己解析 HTTP status |
两种方式完全等价,SDK 只是封装了 HTTP 调用和自动刷新逻辑,不引入任何额外的平台绑定。
下一步
API 参考
查看完整的 OAuth 和 Events API 端点