Open Platform
开发者 API 文档
壹号大联盟开放平台接口标准。第三方网站接入后,用户可用联盟账号一键登录你的网站,积分消耗按你网站的 积分结算系数 为你结算收入。
提供 官方 7 语言 SDK(zip 下载,含源码 + README + examples)和 7 语言 HTTP 示例,按你的技术栈选其一。
1. SDK 下载(7 语言官方版本)
联盟官方维护 7 个语言版本,接口签名完全一致。 每个 zip 包内含:源码 + README(中文)+ LICENSE(MIT)+ examples/(完整可运行示例)。
📦 选语言 / 下载 / 安装
✓ 官方npm
npm install alliance-sdk引入方式
import Alliance from "alliance-sdk";SDK 优势:自动处理 Basic 鉴权 / URL 拼接 / JSON 解析 / 业务错误码封装 —— 你直接
consume_points() 即可,不用关心 HTTP 细节。2. 登录验证
子站与联盟主站登录打通有两种方式(SSO 模式 A / 模式 B)。二选一即可,按你的业务流程选最合适的。
模式 B(推荐)
用户从主站点过来 → 主站签发 SSO ticket → 跳回你的网站 → 你服务端 verify
模式 A
你有自己的登录页,主动调 /api/open/login-status 读取联盟登录态
顶部 Tab 切换「SDK 示例」与「HTTP 示例」,下方每个接口都同时呈现两种接入方式。
2.1 校验 SSO 票(模式 B,后端核心接口)
你的服务端拿到
alliance_token 后调用校验接口解密(凭证 5 分钟有效、一次性使用)。🚀 SDK 调用示例
// /api/sso/landing?alliance_token=xxx
export async function POST(req: Request) {
const url = new URL(req.url);
const token = url.searchParams.get("alliance_token");
if (!token) return Response.redirect("/login");
try {
const user = await alliance.verifySsoToken(token);
// user: { uid, email, username, avatar, is_lifetime, points, ... }
await createSession(user.uid); // 在你的数据库创建/绑定账号 + cookie
return Response.redirect("/dashboard");
} catch (e: any) {
if (e.code === "INVALID_TICKET") return Response.redirect("/login?error=expired");
throw e;
}
}2.2 模式 A:读取联盟登录态
保留独立登录页时,主动读取联盟主站 cookie 中的登录态。
🚀 SDK 调用示例
// /api/auth/alliance 路由:把前端浏览器 cookie 里的 al_token 转发到主站
export async function POST(req: Request) {
const { al_token } = await req.json();
const status = await alliance.getLoginStatus(al_token);
if (!status.loggedIn) {
return Response.json({ loggedIn: false });
}
return Response.json(status);
// status.user: { uid, email, username, avatar, is_lifetime, points }
}3. OAuth2.1 授权
联盟实现完整 OAuth2.1 Provider:授权码模式 + 强制 PKCE(S256)。 你需要先注册 client(client_id / client_secret / redirect_uris)。 适用于"我的应用是正规第三方,需要标准 OAuth 流程"的场景。
顶部 Tab 切换「SDK 示例」与「HTTP 示例」,下方每个接口都同时呈现两种接入方式。
3.1 OAuth2.1 完整流程
🚀 SDK 调用示例
import * as crypto from "crypto";
// 1. 生成 PKCE + state(实际场景下 state/verifier 存 session 再重定向)
const verifier = base64url(crypto.randomBytes(32));
const challenge = base64url(crypto.createHash("sha256").update(verifier).digest());
const state = base64url(crypto.randomBytes(16));
// 2. 引导用户跳转到 authorize
const authUrl = `https://www.all001.com/api/oauth/authorize?`
+ `response_type=code&client_id=${CID}&redirect_uri=${encodeURIComponent(REDIRECT)}`
+ `&state=${state}&code_challenge=${challenge}&code_challenge_method=S256`;
// res.redirect(authUrl);
// 3. /callback 拿到 code 后换 token
const { code } = req.query;
const tokens = await alliance.exchangeCode({
code: String(code),
redirectUri: REDIRECT,
codeVerifier: verifier,
});
// tokens.access_token / refresh_token / expires_in
// 4. 拿 userinfo
const me = await alliance.getUserInfo(tokens.accessToken);4. 积分接口
查询用户信息、查询积分明细、扣减积分(核心业务)。所有接口走 HTTP Basic
base64(client_id:client_secret)。顶部 Tab 切换「SDK 示例」与「HTTP 示例」,下方每个接口都同时呈现两种接入方式。
4.1 用户/会员信息查询
🚀 SDK 调用示例
try {
const user = await alliance.getUser(2);
console.log(user);
// {
// uid: 2,
// email: "[email protected]",
// username: "演示用户",
// avatar: null,
// is_lifetime: true,
// points: 1280,
// balance: "36.80",
// member_since: "2026-01-01T00:00:00.000Z",
// }
} catch (e: any) {
if (e.code === "NOT_FOUND") console.log("用户不存在");
else throw e;
}4.2 积分明细
🚀 SDK 调用示例
const items = await alliance.getPointsLog(2, 50);
for (const it of items) {
// it: { id, amount, points_after, type, note, created_at }
console.log(`${it.amount > 0 ? "+" : ""}${it.amount} ${it.note ?? ""}`);
}4.3 积分消耗(核心业务)
用户在你网站使用付费功能时调用。接口特性:order_ref 幂等(重复提交返回409,不会重复扣)、Redis 原子预扣(高并发防超扣)、积分不足返回 402。 扣减自动按你网站的积分结算系数记账结算。
🚀 SDK 调用示例
try {
const result = await alliance.consumePoints({
uid: 2, // 联盟用户 ID
points: 10, // 消耗积分数
action: "pdf2word", // 业务动作标识
siteId: 5, // 主站后台分配的 site_id
note: "PDF 转 Word",
});
console.log(`✓ 扣分成功,剩余 ${result.remaining}`);
// 放行业务逻辑 ↓↓↓
} catch (e: any) {
switch (e.code) {
case "INSUFFICIENT_POINTS":
console.log("积分不足,引导用户去联盟充值");
break;
case "DUPLICATE_ORDER_REF":
console.log("幂等拦截,视为已成功");
break; // 仍然放行业务
case "NOT_FOUND":
console.log("用户不存在");
break;
default:
throw e;
}
}5. SSO 凭证格式(底层参考)
本节是 SSO 凭证的底层结构参考,跟前面的"登录验证"是不同维度—— 第 2 节讲怎么调用,本节讲凭证长什么样。 如果你只用 SDK / verify 接口,无需关心此节。
用户在联盟主站点击你的网站卡片 → 主站签发一次性加密凭证(SSO ticket)→ 302 跳转到:
URL
https://your-site.com/api/sso/landing?alliance_token=<base64url加密凭证>凭证为 AES-256-GCM 加密的 JSON,编码结构:
结构
base64url( IV(12字节) + AuthTag(16字节) + Ciphertext )
明文JSON字段:
{
"uid": number, // 联盟用户ID
"email": string, // 邮箱
"username": string, // 昵称
"avatar": string|null, // 头像URL
"is_lifetime": 0|1, // 是否终身会员
"points": number, // 当前积分
"ts": number, // 签发时间戳(秒),超过300秒视为过期
"nonce": string // 随机串,防重放(同一nonce只可消费一次)
}推荐优先调用
POST /api/sso/verify (无需持有密钥、自带一次性防重放);自行解密适合内网/离线场景,需自己实现 nonce 防重放。6. OpenAPI 3.0 规范(机器可读)
这份规范是机器可读的接口定义。 你可以:
- 下载 YAML → 导入 Postman / Apifox / Swagger UI
OpenAPI 3.0.3 · 可导入 Postman / Apifox / 自动生成任意语言 SDK
openapi: 3.0.3
info:
title: 会员联盟开放平台 API
description: |
会员联盟 (`all001`) 开放平台接口规范。
第三方网站接入后,用户可用联盟账号一键登录,积分消耗按 `points_rate` 系数结算。
## 接入方式(按推荐度排序)
1. **Node.js SDK** —— `npm install alliance-sdk`,5 个方法即用(强烈推荐)
2. **OpenAPI 规范** —— 本文件,可被 [OpenAPI Generator](https://openapi-generator.tech) 一键生成任意语言 SDK
3. **直接 HTTP 调用** —— 任何能发 HTTP 的语言都行
## 接入模式
- **A** 保留独立登录页:`GET /api/open/login-status?token=`
- **B** 跳转联盟登录自动返回:`?alliance_token=` → `POST /api/sso/verify`(推荐)
- **C** OAuth2.1 授权码 + 强制 PKCE(S256):`/api/oauth/{authorize,token,userinfo,jwks}`
## 鉴权
所有 `/api/open/*` 与 `/api/oauth/*` 端点需要 HTTP Basic:
```
Authorization: Basic base64(client_id:client_secret)
```
client 凭据在主站后台「应用凭证」页申请,与子站 `site_id` 绑定。
version: "1.0.0"
contact:
name: 会员联盟开放平台
url: https://www.all001.com/api-docs
email: [email protected]
license:
name: MIT
servers:
- url: https://www.all001.com
description: 生产环境
tags:
- name: SSO
description: SSO 单点登录(模式 A + B)
- name: OAuth2.1
description: OAuth2.1 授权码 + PKCE(模式 C)
- name: User
description: 用户信息查询
- name: Points
description: 积分查询、扣减、明细
- name: Meta
description: 元信息(JWKS、客户端信息)
security:
- basicAuth: []
paths:
# ────────────────────────────────────────────────────────────
# SSO 模式 A:读取联盟登录态
# ────────────────────────────────────────────────────────────
/api/open/login-status:
get:
tags: [SSO]
summary: 读取联盟登录态(模式 A)
description: |
子站保留自己的登录页时,用此接口读取联盟主站的登录状态。
用户可能已在联盟主站登录(cookie `al_token`),前端获取后传给本接口。
security:
- basicAuth: []
parameters:
- name: token
in: query
required: true
schema: { type: string }
description: 联盟主站 cookie `al_token` 的值
responses:
"200":
description: 成功
content:
application/json:
schema:
oneOf:
- $ref: "#/components/schemas/LoginStatusLoggedIn"
- $ref: "#/components/schemas/LoginStatusLoggedOut"
examples:
loggedIn:
value:
ok: true
loggedIn: true
user:
uid: 2
email: [email protected]
username: 演示用户
avatar: null
is_lifetime: true
points: 1280
loggedOut:
value:
ok: true
loggedIn: false
"401":
$ref: "#/components/responses/Unauthorized"
# ────────────────────────────────────────────────────────────
# SSO 模式 B:跳转票签发
# ────────────────────────────────────────────────────────────
/api/sso/ticket:
get:
tags: [SSO]
summary: 签发 SSO 跳转票(模式 B)
description: |
用户从主站点击「联盟一键登录」按钮时调用。
返回子站跳转 URL(含 `?alliance_token=...`,5 分钟有效、一次性)。
⚠️ 此端点不需要 Basic 鉴权,但要登录态(cookie `al_token`)。
security: []
parameters:
- name: site_id
in: query
required: true
schema: { type: integer }
description: 子站 sites.id(主站后台分配)
- name: next
in: query
required: false
schema: { type: string }
description: 登录成功后跳转回子站的相对路径
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
redirect:
type: string
format: uri
example: "https://partner.com/landing?alliance_token=xxx"
"401":
description: 用户未登录主站
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [false] }
needLogin: { type: boolean, enum: [true] }
# ────────────────────────────────────────────────────────────
# SSO 模式 B:跳转票校验
# ────────────────────────────────────────────────────────────
/api/sso/verify:
post:
tags: [SSO]
summary: 校验 SSO 跳转票(模式 B,子站核心接口)
description: |
子站从 query 中拿到 `alliance_token` 后,调用本接口解密。
票一次性使用:成功后立即失效。
security:
- basicAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [token]
properties:
token:
type: string
description: 加密票(base64url 编码)
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
user: { $ref: "#/components/schemas/AllianceUser" }
"401":
description: 凭证无效或已使用
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorResponse" }
# ────────────────────────────────────────────────────────────
# 用户信息
# ────────────────────────────────────────────────────────────
/api/open/user:
get:
tags: [User]
summary: 查询用户信息(含积分、会员状态)
security:
- basicAuth: []
parameters:
- name: uid
in: query
required: true
schema: { type: integer }
description: 联盟用户 ID
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
user: { $ref: "#/components/schemas/AllianceUserWithMemberSince" }
"401":
$ref: "#/components/responses/Unauthorized"
"404":
$ref: "#/components/responses/NotFound"
# ────────────────────────────────────────────────────────────
# 积分明细
# ────────────────────────────────────────────────────────────
/api/open/points/log:
get:
tags: [Points]
summary: 查询积分明细
security:
- basicAuth: []
parameters:
- name: uid
in: query
required: true
schema: { type: integer }
- name: limit
in: query
required: false
schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
items:
type: array
items: { $ref: "#/components/schemas/PointsLogItem" }
"401":
$ref: "#/components/responses/Unauthorized"
# ────────────────────────────────────────────────────────────
# 积分扣减(核心业务接口)
# ────────────────────────────────────────────────────────────
/api/open/points/consume:
post:
tags: [Points]
summary: 扣减用户积分(幂等)
description: |
## 关键特性
- **幂等**:`order_ref` 唯一,重复提交返回 409,不会重复扣
- **Redis 原子预扣**:高并发防超扣
- **自动结算**:按子站 `points_rate` 系数自动写入 `settlement_log`
## 业务错误码
| HTTP | 含义 | 处理建议 |
|---|---|---|
| 200 | 扣减成功 | 放行业务逻辑 |
| 402 | 积分不足 | 引导用户去联盟充值/签到 |
| 409 | order_ref 重复 | 视为已成功,勿重试换单号 |
security:
- basicAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [uid, points, order_ref]
properties:
uid:
type: integer
description: 联盟用户 ID
points:
type: integer
minimum: 1
description: 消耗积分数
order_ref:
type: string
maxLength: 64
description: 子站侧唯一订单号(≤64 字符)
note:
type: string
description: 用途说明,会展示给用户
example:
uid: 2
points: 10
order_ref: "5-pdf2word-7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c"
note: "PDF 转 Word"
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
message: { type: string, example: "扣减成功" }
points_after: { type: integer }
remaining: { type: integer }
order_ref: { type: string }
"402":
description: 积分不足
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorResponse" }
"409":
description: order_ref 重复(幂等拦截)
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorResponse" }
# ────────────────────────────────────────────────────────────
# OAuth2.1:授权端点
# ────────────────────────────────────────────────────────────
/api/oauth/authorize:
post:
tags: [OAuth2.1]
summary: OAuth2.1 授权端点(PKCE S256 强制)
description: |
引导用户从子站跳到 `https://www.all001.com/api/oauth/authorize?response_type=code&...`,
用户在主站确认授权后回调 `redirect_uri?code=xxx&state=xxx`。
授权码 60 秒有效、一次性。
security: []
parameters: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
[client_id, redirect_uri, code_challenge, code_challenge_method]
properties:
client_id: { type: string }
redirect_uri: { type: string, format: uri }
scope: { type: string, default: "openid profile" }
state: { type: string }
code_challenge: { type: string }
code_challenge_method: { type: string, enum: [S256] }
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
ok: { type: boolean, enum: [true] }
redirect: { type: string, format: uri }
/api/oauth/token:
post:
tags: [OAuth2.1]
summary: OAuth2.1 令牌端点
security:
- basicAuth: []
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
oneOf:
- type: object
required: [grant_type, code, redirect_uri, code_verifier]
properties:
grant_type: { type: string, enum: [authorization_code] }
code: { type: string }
redirect_uri: { type: string }
code_verifier: { type: string }
- type: object
required: [grant_type, refresh_token]
properties:
grant_type: { type: string, enum: [refresh_token] }
refresh_token: { type: string }
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
access_token: { type: string, description: JWT RS256, 1 小时有效 }
refresh_token: { type: string }
expires_in: { type: integer, example: 3600 }
token_type: { type: string, example: Bearer }
/api/oauth/userinfo:
get:
tags: [OAuth2.1]
summary: OAuth2.1 用户信息端点
security:
- bearerAuth: []
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
sub: { type: string }
email: { type: string }
username: { type: string }
is_lifetime: { type: boolean }
/api/oauth/jwks:
get:
tags: [OAuth2.1, Meta]
summary: OAuth2.1 JWKS 公钥(用于本地验签 JWT)
security: []
responses:
"200":
description: 成功
content:
application/json:
schema:
type: object
properties:
keys:
type: array
items:
type: object
properties:
kty: { type: string, example: RSA }
kid: { type: string, example: all001-oauth-key-1 }
alg: { type: string, example: RS256 }
use: { type: string, example: sig }
/api/oauth/client-info:
get:
tags: [OAuth2.1, Meta]
summary: 查询 OAuth client 公开信息(授权前展示应用信息)
security: []
parameters:
- name: client_id
in: query
required: true
schema: { type: string }
responses:
"200":
description: 成功
components:
securitySchemes:
basicAuth:
type: http
scheme: basic
description: HTTP Basic,`base64(client_id:client_secret)`
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
schemas:
AllianceUser:
type: object
properties:
uid: { type: integer }
email: { type: string, format: email }
username: { type: string }
avatar: { type: string, nullable: true }
is_lifetime: { type: boolean, description: "是否终身会员" }
points: { type: integer }
balance: { type: string, description: "余额(元,字符串防精度丢失)" }
AllianceUserWithMemberSince:
allOf:
- $ref: "#/components/schemas/AllianceUser"
- type: object
properties:
member_since:
type: string
format: date-time
description: 注册时间
LoginStatusLoggedIn:
type: object
properties:
ok: { type: boolean, enum: [true] }
loggedIn: { type: boolean, enum: [true] }
user: { $ref: "#/components/schemas/AllianceUser" }
LoginStatusLoggedOut:
type: object
properties:
ok: { type: boolean, enum: [true] }
loggedIn: { type: boolean, enum: [false] }
PointsLogItem:
type: object
properties:
id: { type: integer }
amount: { type: integer, description: "正为收入,负为支出" }
points_after: { type: integer }
type:
type: string
enum: [register, signin, recharge, consume, invite_reward, admin]
note: { type: string, nullable: true }
created_at: { type: string, format: date-time }
ErrorResponse:
type: object
properties:
ok: { type: boolean, enum: [false] }
message: { type: string }
responses:
Unauthorized:
description: 鉴权失败
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorResponse" }
NotFound:
description: 资源不存在
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorResponse" }
7. 通用错误码
所有错误响应统一结构:
{ "ok": false, "message": "错误描述" }SDK 模式下,错误会包成
AllianceError 类,附带语义化 e.code字符串(INSUFFICIENT_POINTS / DUPLICATE_ORDER_REF / INVALID_TICKET 等),推荐优先用 e.code 做业务分支。准备好接入了?提交开发者入驻申请,审核通过即发放 client 凭证。
🚀 立即申请入驻