Files
superDreamFront/docs/sub2api_api.md
xuyong 35c0b7de16 integrate sub2api as upstream for auth/keys/usage via FastAPI BFF
Preserve local user table for superDream-specific features while syncing
user lifecycle, API key CRUD and usage queries through sub2api. Admin token
handles reads and user lifecycle; per-user tokens (Fernet-encrypted in DB)
handle key writes that admin endpoints do not expose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 21:23:08 +08:00

12 KiB
Raw Blame History

sub2api 接口对接规范

本文档记录 superDreamFront 与 sub2api 对接所使用的全部 HTTP 接口,以及 BFF 层superDreamFront FastAPI各服务的调用策略。

对应的 sub2api 源码位置:

  • 路由:sub2api/backend/internal/server/routes/{auth,user,admin}.go
  • 用户侧 handlersub2api/backend/internal/handler/{auth_handler,api_key_handler,usage_handler,user_handler}.go
  • 管理员 handlersub2api/backend/internal/handler/admin/{user_handler,apikey_handler}.go
  • DTO 类型:sub2api/backend/internal/handler/dto/types.go

1. 通用约定

1.1 Base URL

所有接口以 /api/v1 为前缀,完整地址由环境变量 SD_SUB2API_BASE_URL 决定,例如:

SD_SUB2API_BASE_URL = http://127.0.0.1:8080
→ 实际请求http://127.0.0.1:8080/api/v1/...

1.2 响应 envelope

所有响应统一包一层(来源 internal/pkg/response/response.go

{
  "code": 0,                // 0 表示成功;非 0 为 HTTP 状态码
  "message": "success",
  "reason": "",             // 仅错误时出现
  "metadata": null,         // 错误附加信息
  "data": { ... }           // 业务载荷
}

分页响应的 data 形状固定为:

{
  "items": [...],
  "total": 1234,
  "page": 1,
  "page_size": 20,
  "pages": 62
}

BFF 层建议在 sub2api.client.request() 中统一解 envelope

  • code == 0 → 返回 data
  • code != 0 → 抛自定义异常 Sub2APIError(code, message, reason, metadata)

1.3 认证方式

sub2api 有三类鉴权:

鉴权类型 携带方式 用途
公开 注册、登录、忘记密码、验证码
用户 JWT Authorization: Bearer <access_token> /keys/usage/user/*
管理员 x-api-key: <admin_api_key>Authorization: Bearer <admin_jwt> /admin/*

管理员 API Key 在 sub2api 后台 Admin 设置 → Admin API Key → 生成 后拿到,写入 SD_SUB2API_ADMIN_TOKEN

1.4 Turnstile

部分公开接口会校验 Cloudflare Turnstile。sub2api 管理员可在设置里关闭;本 BFF 暂不处理 Turnstile若 sub2api 启用会 400。


2. 认证(公开接口)

2.1 发送邮箱验证码

POST /api/v1/auth/send-verify-code

请求:

{ "email": "user@example.com", "turnstile_token": "" }

响应:

{ "message": "Verification code sent successfully", "countdown": 60 }

2.2 注册

POST /api/v1/auth/register

请求:

{
  "email": "user@example.com",
  "password": "******",        // min 6
  "verify_code": "123456",     // 若 sub2api 开启邮箱验证码注册
  "turnstile_token": "",
  "promo_code": "",            // 可选注册优惠码
  "invitation_code": ""        // 可选邀请码
}

响应(同 AuthResponse

{
  "access_token": "...",
  "refresh_token": "...",
  "expires_in": 3600,          // access 有效期(秒)
  "token_type": "Bearer",
  "user": { ...dto.User }
}

2.3 登录

POST /api/v1/auth/login

请求:

{ "email": "user@example.com", "password": "******", "turnstile_token": "" }

若用户未开 2FA响应同注册。 若开启 2FA响应

{ "requires_2fa": true, "temp_token": "...", "user_email_masked": "u**@e**.com" }

此时前端需再调 POST /api/v1/auth/login/2fa

{ "temp_token": "...", "totp_code": "123456" }

2.4 刷新 Token

POST /api/v1/auth/refresh

// 请求
{ "refresh_token": "..." }
// 响应
{ "access_token": "...", "refresh_token": "...", "expires_in": 3600, "token_type": "Bearer" }

注意sub2api 的 refresh_token 一次性使用、每次刷新会 rotateBFF 取到新 refresh_token 要覆盖旧值。

2.5 登出

POST /api/v1/auth/logout body 可选 {"refresh_token": "..."},服务端会撤销该 token。

2.6 忘记 / 重置密码

  • POST /api/v1/auth/forgot-password {"email", "turnstile_token"}
  • POST /api/v1/auth/reset-password {"email", "token", "new_password"}

2.7 当前用户

GET /api/v1/auth/me 需要用户 JWT响应 dto.User + {run_mode}


3. API Key用户 JWT

路由前缀 /api/v1/keys(注意不是 /api-keyshandler 注释里旧路径是误导)。

3.1 列表

GET /api/v1/keys?page=1&page_size=20&sort_by=created_at&sort_order=desc&search=&status=&group_id=

响应 data(分页 envelope

{
  "items": [ { ...dto.APIKey } ],
  "total": 5, "page": 1, "page_size": 20, "pages": 1
}

dto.APIKey 关键字段(完整字段见 types.go

{
  id: number,
  user_id: number,
  key: string,                  // 完整 key注意sub2api 确实返回明文完整 key不做截断
  name: string,
  group_id: number | null,
  status: "active" | "inactive",
  ip_whitelist: string[], ip_blacklist: string[],
  last_used_at: string | null,
  quota: number,                // USD0 = 无限
  quota_used: number,
  expires_at: string | null,

  rate_limit_5h: number, rate_limit_1d: number, rate_limit_7d: number,  // 0=不限
  usage_5h: number, usage_1d: number, usage_7d: number,
  reset_5h_at, reset_1d_at, reset_7d_at: string | null,

  created_at, updated_at: string,
  group?: dto.Group
}

3.2 单个查询

GET /api/v1/keys/:iddto.APIKey

3.3 新建

POST /api/v1/keys

{
  "name": "my-key",
  "group_id": 1,                 // 可选
  "custom_key": null,            // 可选自定义 key
  "ip_whitelist": [], "ip_blacklist": [],
  "quota": 0,                    // 0=无限
  "expires_in_days": null,       // null=不过期
  "rate_limit_5h": 0, "rate_limit_1d": 0, "rate_limit_7d": 0
}

响应:dto.APIKey

3.4 更新

PUT /api/v1/keys/:id(字段同上,全部可选;特殊:expires_at="" 清除过期,reset_quota=true 重置已用,reset_rate_limit_usage=true 重置限速计数)

3.5 删除

DELETE /api/v1/keys/:id{ "message": "API key deleted successfully" }

3.6 用户可用分组 / 专属倍率

  • GET /api/v1/groups/availabledto.Group[]
  • GET /api/v1/groups/rates{ [groupID: number]: number }

4. 用量(用户 JWT

4.1 列表

GET /api/v1/usage?page=&page_size=&api_key_id=&model=&request_type=&stream=&billing_type=&start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&timezone=Asia/Shanghai

响应 items: dto.UsageLog[](分页)。

dto.UsageLog 关键字段:

{
  id, user_id, api_key_id, account_id: number,
  request_id, model: string,
  input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens,
  input_cost, output_cost, cache_creation_cost, cache_read_cost, total_cost, actual_cost,
  request_type: string,      // "chat"/"responses"/"messages"/"image" 等
  stream: boolean,
  duration_ms, first_token_ms: number | null,
  image_count: number,
  created_at: string
}

4.2 详情

GET /api/v1/usage/:iddto.UsageLog

4.3 统计

GET /api/v1/usage/stats?period=today|week|monthstart_date&end_date → 返回 service.UsageStats(求和 + tokens/requests/cost 各纬度汇总;字段见 sub2api UsageStats 定义)。

4.4 Dashboard

  • GET /api/v1/usage/dashboard/stats
  • GET /api/v1/usage/dashboard/trend?granularity=day&start_date&end_date
  • GET /api/v1/usage/dashboard/models?start_date&end_date
  • POST /api/v1/usage/dashboard/api-keys-usage body { "api_key_ids": [1,2,3] }

5. 管理员接口x-api-key

superDream BFF 仅用到以下 admin 接口:

5.1 用户

  • POST /api/v1/admin/users — 注册同步
    { "email", "password", "username", "notes": "from superDream",
      "balance": 0, "concurrency": 0, "allowed_groups": [] }
    
    dto.AdminUser
  • PUT /api/v1/admin/users/:id — 改密/改状态同步(字段均为可选 PATCH 语义)
  • DELETE /api/v1/admin/users/:id — 删除同步
  • GET /api/v1/admin/users?search=email@x.com — 查找用户(拿 id 用于后续调用)
  • GET /api/v1/admin/users/:id — 详情

5.2 查用户 API Keys / 用量

  • GET /api/v1/admin/users/:id/api-keys?page=&page_size=&sort_by=&sort_order=dto.APIKey[](分页)
  • GET /api/v1/admin/users/:id/usage?period=today|week|monthUsageStats

5.3 能力缺口

sub2api admin 路径不提供 代用户创建/删除/修改 Key 的接口(registerAdminAPIKeyRoutesPUT /admin/api-keys/:id 改分组)。
→ Key 的写操作必须使用用户 JWT。


6. BFF 对接策略

6.1 本地 User 表扩展字段

字段 类型 说明
sub2api_user_id BIGINT 对应 sub2api 的 dto.User.id
sub2api_refresh_token_enc TEXT Fernet 加密后的 refresh_token
sub2api_access_token TEXT 当前有效的 access_token内存级缓存即可但写库便于重启复用
sub2api_access_expires_at DATETIME access_token 过期时间UTC

6.2 鉴权 → Token 获取顺序

每次 BFF 需要代理到 sub2api 用户接口 时:

  1. 如本地缓存 access_token 未过期(预留 60s 余量),直接用
  2. 否则用 Fernet 解密 refresh_token/auth/refresh 换新rotate 后写回)
  3. 若 refresh 也 401token 被撤销)→ 返回前端 401 sub2api_reauth_required,前端弹窗让用户重新输密码,后端重调 /auth/login 重新拿到 token

6.3 各业务同步矩阵

动作 本地 sub2apiadmin token sub2api用户 token
注册 ①写 userpending POST /admin/users 取回 id POST /auth/login 拿首对 tokens
④成功后 commit 本地;任一失败整体回滚
登录 ①校验本地密码 → 签本地 JWT ②同步调 /auth/login 刷新存储 refresh_token
改密 ①更新本地 hash PUT /admin/users/:id ③用新密码调 /auth/login 替换 token
删用户 ①级联删 DELETE /admin/users/:id
GET /keys GET /admin/users/:id/api-keys
POST/PUT/DELETE /keys /keys 系列
GET /usage/* GET /admin/users/:id/usage 兜底:若 admin 接口粒度不够,回退到用户 token 调 /usage
Dashboard 统计 本地聚合 sub2api 返回 /admin/users/:id/usage /usage/dashboard/*

6.4 前端字段替换

替换 frontend/src/services/ 下的 TS interface使其与 dto.APIKey / dto.UsageLog / dto.User 完全一致(字段名用 snake_case 保留,不转 camelCase避免映射层。具体列在实现阶段列出 diff。

6.5 错误映射

sub2api 返回 BFF 抛出 前端显示
code=400 HTTPException 400 + reason 表单校验提示
code=401 INVALID_TOKEN/TOKEN_EXPIRED 触发 refresh 流;失败抛 401 sub2api_reauth_required 弹框"请重新登录"
code=403 403 "没有权限"
code=404 404 "资源不存在"
code>=500 502 upstream_error + 日志 "上游服务异常"
网络异常 504 upstream_timeout "请求超时"

7. 新增配置项

写入 app/config/settings.py

sub2api_base_url: str = "http://127.0.0.1:8080"
sub2api_admin_token: str = ""                  # sub2api Admin API Key
sub2api_request_timeout: float = 10.0          # 秒
sub2api_turnstile_bypass: bool = True          # 是否绕过 turnstile需 sub2api 端也关)
token_encryption_key: str = ""                 # Fernet key44 字节 urlsafe base64

生成 Fernet key

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

requirements.txt 增加:httpx>=0.27 cryptography>=42