QCS开放接口文档
QCS(QSL认证系统,QSL Certification System)对外开放接口文档:
基础信息
API地址
https://qcs.qsl.pub/api
基础响应模块
/**
* API响应通用类型定义
* @template T - 响应数据的类型
*/
export interface ApiResponse<T> {
/** 响应数据 */
data: T;
/** 状态码 */
code: number;
/** 消息提示 */
msg: string;
}
code状态码
| 状态码 | 描述 |
|---|---|
| 200 | 正常返回 |
| 500 | 系统错误 |
| 401 | 未授权 |
| 404 | 找不到资源 |
默认请求数据类型
application/json
默认响应数据类型
json
通用数据结构
StationItem
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| id | 台站ID | number |
| callsign | 呼号 | string |
| dxcc | DXCC实体 | string |
| grid | 网格 | string |
| itu | ITU区域 | string |
| cq | CQ区域 | string |
| station_location | 台站位置 | string |
| first_qso_date | 首次QSO时间(毫秒时间戳) | number |
| last_qso_date | 最后QSO时间(毫秒时间戳) | number | null |
QsoList
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|---|---|---|---|
| callsign | 对方呼号 | true | string |
| worked | 我的呼号 | true | string |
| date | 通联时间(时间戳) | true | number |
| mode | 模式 | true | string |
| band | 接收波段 | true | string |
| band_tx | 发送波段 | false | string |
| freq | 接收频率 | true | number |
| freq_tx | 发送频率 | false | number |
| rst_rcvd | 接收RST | true | string |
| rst_sent | 发送RST | true | string |
| satellite | 卫星名称 | false | string |
| propagation | 传播模式 | false | string |
OAuth模块
OAuth模块部分数据使用签名+加密传输。调用受保护接口时需要在请求头中携带:
Authorization: Bearer <access_token>
建议按以下顺序接入:获取授权码 -> 获取Token -> 刷新Token -> 调用资源接口。
OAuth授权码模式说明
- 当前仅支持
response_type=code - 授权请求必须携带
state - 当前支持两种 PKCE 方法:
S256、SM3 - 授权码与
client_id、redirect_uri、scope、code_challenge绑定 - 换取 token 时,必须提交原始
code_verifier
获取授权码
QCS 使用授权页完成用户登录、授权确认与授权码签发。第三方客户端应将用户浏览器跳转到 QCS 授权地址,并携带以下查询参数:
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|---|---|---|---|
| client_id | 客户端ID | true | string |
| redirect_uri | 回调地址,必须与应用登记值完全一致 | true | string |
| response_type | 固定为 code |
true | string |
| scope | 申请的权限范围,逗号分隔 | true | string |
| state | 客户端生成的随机态值,用于回调校验 | true | string |
| code_challenge | PKCE 挑战值 | true | string |
| code_challenge_method | PKCE 方法,支持 S256 / SM3 |
true | string |
授权成功后,QCS 将通过 redirect_uri 回跳并附带:
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| code | 授权码 | string |
| state | 原样回传的 state | string |
scope取值
| scope | 说明 |
|---|---|
| basic | 读取基础信息(openid、callsign) |
| station | 读取台站列表 |
| qsl | 读取QSL统计信息 |
| qso | 导入QSO记录 |
获取Token
POST /oauth/token
请求说明
- 外层请求体使用“先
SM4-GCM加密,再SM2withSM3签名”的方式传输 code作为外层明文字段传输data中的code需与外层code保持一致
请求参数
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|---|---|---|---|
| code | 授权码(明文) | true | string |
| iv | GCM 初始向量,base64;解码后为 12 字节随机值 | true | string |
| data | 加密后的明文请求体,base64 | true | string |
| authtag | GCM 认证标签,base64 | true | string |
| signature | 签名值 | true | string |
| timestamp | 时间戳(秒) | true | number |
| nonce | 随机字符串 | true | string |
data(明文解密后)
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|---|---|---|---|
| client_id | 客户端ID | true | string |
| client_secret | 客户端密钥 | true | string |
| code | 授权码,必须与外层 code 一致 |
true | string |
| redirect_uri | 回调地址,必须与授权时一致 | true | string |
| code_verifier | PKCE 原始校验串 | true | string |
响应参数
响应体同样使用签名 + SM4-GCM 加密;客户端需先验签,再按同一组参数解密。明文结构如下:
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| access_token | 访问令牌 | string |
| refresh_token | 刷新令牌 | string |
| token_type | 固定为 Bearer |
string |
| expires_in | 过期时间戳(秒) | number |
刷新Token
POST /oauth/refresh_token
请求头
Authorization: Bearer <refresh_token>
说明
- refresh token 为一次性轮换凭证
- 刷新成功后,旧 refresh token 立即失效
- 客户端必须使用响应中的新 refresh token 覆盖本地旧值
响应参数
响应格式与 /oauth/token 相同,明文结构也相同。
请求说明
- 使用
refresh_token作为 Bearer Token 调用 - 刷新成功后,旧 refresh token 立即失效
- 新的 access token / refresh token 会一起返回
获取用户信息
POST /oauth/userinfo
scope
basic
请求头
Authorization: Bearer <access_token>
请求说明
- 响应体使用签名 +
SM4-GCM加密
响应参数(明文解密后)
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| openid | 当前用户的 OAuth openid | string |
| callsign | 当前用户呼号 | string |
获取QSL统计信息
POST /oauth/qsl_info
scope
qsl
请求头
Authorization: Bearer <access_token>
请求说明
- 响应体使用签名 +
SM4-GCM加密
响应参数(明文解密后)
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| worked | 主呼号 | string |
| qso_total | QSO总数 | number |
| qsl_total | QSL总数 | number |
| latest_qsl_hash | 最新QSL哈希 | string | null |
| latest_qsl_date | 最新QSL时间,毫秒时间戳 | number | null |
获取台站列表(OAuth)
POST /oauth/get_station_list
scope
station
请求头
Authorization: Bearer <access_token>
请求说明
- 响应体使用签名 +
SM4-GCM加密
响应参数(明文解密后)
响应为 StationItem[]。
导入QSO记录(OAuth)
POST /oauth/push_qso
scope
qso
请求说明
- 请求体使用签名 +
SM4-GCM加密 data解密后应为导入结构体 JSON
请求参数
| 参数名称 | 参数说明 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|
| iv | 加密向量(base64) | true | string | |
| data | 加密后的密文(base64) | true | string | data |
| authtag | GCM认证标签(base64) | true | string | |
| signature | 签名值 | true | string | |
| timestamp | 时间戳(秒) | true | number | |
| nonce | 随机字符串 | true | string |
data(明文解密后)
| 参数名称 | 参数说明 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|
| station_id | 台站id | true | number | |
| qso_list | qso列表 | true | QsoList[] | QsoList |
请求示例
{
"iv": "...",
"data": "...",
"authtag": "...",
"signature": "...",
"timestamp": 1735660800,
"nonce": "..."
}
响应示例
{
"data": {
"iv": "...",
"data": "...",
"authtag": "...",
"signature": "...",
"timestamp": 1735660800,
"nonce": "..."
},
"code": 200,
"msg": "success"
}
加密签名与验签解密
OAuth 加密接口(如 /oauth/token、/oauth/userinfo、/oauth/get_station_list、/oauth/qsl_info、/oauth/push_qso)统一采用同一套报文约定。
1. 整体流程
- 发送方先组装明文 JSON,再使用
SM4-GCM加密,最后对密文做SM2withSM3签名 - 接收方先校验
timestamp、nonce、signature,验签通过后再执行SM4-GCM解密 - 请求与响应都遵循这一流程,只是双方使用的 SM2 密钥方向不同
2. 字段说明
| 字段 | 说明 |
|---|---|
| iv | Base64 字符串,解码后必须为 12 字节随机值 |
| data | 业务明文经 SM4-GCM 加密后的密文,Base64 编码 |
| authtag | SM4-GCM 认证标签,Base64 编码 |
| signature | 对 data|timestamp|nonce 进行 SM2withSM3 签名后的结果 |
| timestamp | 秒级 Unix 时间戳 |
| nonce | 每次请求/响应唯一的随机字符串,用于防重放 |
| sn | 对称密钥编号,参与 AAD 计算;无编号时使用空字符串 |
3. 明文封装格式
加密前,明文统一封装为:
{
"data": { ...业务数据... },
"_n": "<nonce>",
"_appid": "<appid>",
"_timestamp": 1735660800
}
其中:
data为真实业务数据_appid必须与当前客户端appid一致_timestamp必须与外层timestamp一致_n建议与外层nonce保持一致
4. SM4-GCM 加密参数
- 对称算法:
SM4-GCM - 密钥:16 字节 SM4 密钥,接口传输和配置中统一使用 Base64 表示
iv:12 字节随机值,Base64 编码后放入ivauthtag:GCM 输出的认证标签,Base64 编码后放入authtag- AAD 固定为:
{urlpath}|{appid}|{timestamp}|{sn}
说明:
urlpath为接口路径本身,例如/oauth/tokenappid为客户端client_idtimestamp必须与报文外层字段一致sn为对称密钥编号;如果接口场景没有编号,则传空字符串参与拼接
只要 urlpath、appid、timestamp、sn 任一项不一致,都会导致解密或认证失败。
5. 签名规则
签名原文固定为:
{data}|{timestamp}|{nonce}
签名说明:
data指加密后的密文字段,不是明文 JSONtimestamp、nonce取报文外层字段- 签名算法为
SM2withSM3
5.1 SM2withSM3 兼容性要求
为避免不同语言/不同国密库之间互通失败,OAuth 模块在签名验签时建议按以下固定配置接入:
- 签名算法:
SM2withSM3 - 公钥格式:未压缩公钥,
04开头的 130 位 Hex 字符串 - 私钥格式:32 字节私钥的 Hex 字符串
- 签名输出格式:推荐使用
DER编码后再做 Base64 - 验签输入格式:推荐使用
DER签名;当前接口兼容DER签名和 64 字节原始签名 - 用户标识(distid / userId):固定为默认值
1234567812345678
其中签名值在本系统中的实际传输格式为:
Base64(DER(signature))
也就是说:
- 签名时,先生成 SM2 签名结果
- 将签名按
DER编码 - 再将 DER 二进制做 Base64,作为
signature字段传输
如果你的 SDK 支持“原始 64 字节签名”和“DER 编码签名”两种模式,优先选择 DER。
5.2 常见 SDK 配置提示
不同 SDK 的参数名可能不同,但语义建议对齐为:
hash = true或useSm3 = trueder = trueuserId/distId/id = "1234567812345678"- 如果存在
cipherMode,在签名场景可忽略;在 SM2 加密场景建议选择C1C3C2
JavaScript 接入推荐使用 sm-crypto-v2 国密库,签名常见写法可参考:
sign(data, privateKey, {
hash: true,
der: true,
})
// userId 默认即可
验签则保持同样的 hash/userId/der 语义。
6. 请求方向的密钥使用
客户端调用 QCS 加密接口时:
- 使用协商好的
SM4密钥加密请求体 - 使用客户端自己的
SM2私钥生成signature - QCS 使用客户端登记的
SM2公钥验签
7. 响应方向的密钥使用
QCS 返回加密响应时:
- 使用同一把
SM4密钥加密响应体 - 使用 QCS 平台
SM2私钥生成signature - 客户端使用 QCS 平台
SM2公钥验签
8. 接入建议
- 先固定一套测试参数,单独验证“加密 -> 签名 -> 验签 -> 解密”链路
- 优先确保
urlpath与实际请求路径完全一致,包含前导/ - 所有 Base64 字段均使用标准 Base64,不要混用 Base64URL
nonce每次请求都重新生成,不要复用- 客户端与服务端时间应保持同步,避免因时钟漂移导致
timestamp校验失败
PAT模块
PAT(Personal Access Token,个人访问令牌)由 QCS 个人中心创建并管理,对外仅开放使用接口。
- PAT 只能在创建时明文返回一次,请客户端自行安全保存
push_qso_by_token的业务数据使用SM4-CBC + PKCS#7加密,PAT 原文直接作为对称密钥使用
PAT加密传输约定
PAT 模式仅对业务数据做对称加密。
1. 整体流程
- 客户端持有 PAT 原文
- 调用方使用 PAT 原文作为
SM4-CBC的密钥加密业务 JSON - 请求时直接在
token字段中传入 PAT 原文
2. 加密规则
- 对称算法:
SM4-CBC - 填充方式:
PKCS#7 - 密钥:PAT 原文
iv:16 字节随机值,Base64 编码后放入请求体ivdata:业务 JSON 加密后的密文,Base64 编码
3. 注意事项
iv必须每次重新生成,不能复用token必须传 PAT 原文,不能传哈希值或掩码值token、iv、data需保持匹配
获取QCS台站列表
POST /oauth/personal_access_tokens/get_station_list
请求参数
| 参数名称 | 参数说明 | 是否必须 | 数据类型 |
|---|---|---|---|
| token | QCS 个人中心获取的 PAT | true | string |
请求说明
- 该接口不需要 OAuth access token
- 直接使用 PAT 访问
请求示例
{
"token": "MTIzNDU2"
}
响应参数
响应为 StationItem[]。
响应示例
{
"data": [
{
"id": 1,
"callsign": "BG7ZAG",
"dxcc": "83",
"grid": "OL50da",
"itu": "44",
"cq": "24",
"station_location": "中国-海南",
"first_qso_date": 1735660800000,
"last_qso_date": null
}
],
"code": 200,
"msg": "success"
}
导入QSO记录(PAT)
POST /oauth/personal_access_tokens/push_qso_by_token
请求参数
| 参数名称 | 参数说明 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|
| token | QCS 个人中心获取的 PAT | true | string | |
| iv | 加密向量(16字节随机IV转 base64) | true | string | |
| data | 加密数据(json字符串密文) | true | string | data |
请求说明
- 该接口不需要 OAuth access token
data使用SM4-CBC + PKCS#7加密- 解密密钥直接使用 PAT 原文
iv为 16 字节随机值的 Base64 表示
data
| 参数名称 | 参数说明 | 是否必须 | 数据类型 | schema |
|---|---|---|---|---|
| station_id | 台站id | true | number | |
| qso_list | qso列表 | true | QsoList[] | QsoList |
请求示例
{
"token": "MTIzNDU2",
"iv": "w==",
"data": "..."
}
const data = {
station_id: 1,
qso_list: [
{
callsign: "BG7ZAG",
worked: "BG7ZAG",
date: 1735660800000,
mode: "SSB",
band: "160m",
freq: 160000000,
rst_rcvd: "59",
rst_sent: "59",
satellite: null,
propagation: null
}
]
}
响应示例
{
"data": "提交成功",
"code": 200,
"msg": "success"
}





