跳转到主要内容

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建表方式时机
SQLAlchemyBase.metadata.create_all(engine)应用启动时自动创建(如果表不存在)
Drizzledrizzle-kit generate + drizzle-kit migrate手动执行迁移命令
SQLAlchemy 的 create_all 只能建新表,不能给已有表加列。如果你升级了 SDK 且 contrib model 有新字段,需要手动 ALTER TABLE 或使用 Alembic 迁移。

Contrib 表结构

profyOAuthToken / profy_oauth_token 表默认字段:
字段类型说明
idserial / integer主键
profy_user_idstring(64)Profy 用户 ID(唯一索引)
access_tokentextJWT Access Token (1h)
refresh_tokentextRefresh Token (90d)
expires_attimestampAccess Token 过期时间
scopestring授权 scope
created_attimestamp创建时间
updated_attimestamp更新时间
表名可自定义:
ProfyOAuthToken = create_token_model(Base, table_name="my_profy_tokens")

错误处理

SDK 提供结构化异常,方便业务层精确处理:
异常HTTP 状态码含义
AuthExpired401Token 过期且 refresh 也失败,需要用户重新授权
InsufficientBalance402用户余额不足
InvalidEvent400事件名未在 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 Exchangeprofy.exchangeCode()自己调 POST /oauth/token
Token RefreshSDK 自动续期自己检测过期 + 手动 refresh
重试 / 幂等SDK 内置 401 重试自己实现
存储contrib 或 自定义回调完全自定义
计费上报profy.reportEvent()自己调 POST /openapi/v1/events
错误分类结构化异常自己解析 HTTP status
两种方式完全等价,SDK 只是封装了 HTTP 调用和自动刷新逻辑,不引入任何额外的平台绑定。

下一步

快速开始

5 分钟完成首次接入

API 参考

查看完整的 OAuth 和 Events API 端点