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/userinfoscope
basic请求头
Authorization: Bearer <access_token>请求说明
- 响应体使用签名 + SM4-GCM 加密
响应参数(明文解密后)
| 参数名称 | 参数说明 | 数据类型 |
|---|---|---|
| openid | 当前用户的 OAuth openid | string |
| callsign | 当前用户呼号 | string |
获取QSL统计信息
POST
/oauth/qsl_infoscope
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_listscope
station请求头
Authorization: Bearer <access_token>请求说明
- 响应体使用签名 + SM4-GCM 加密
响应参数(明文解密后)
响应为
StationItem[]。导入QSO记录(OAuth)
POST
/oauth/push_qsoscope
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 编码后放入 iv
- authtag:GCM 输出的认证标签,Base64 编码后放入 authtag
- AAD 固定为:
{urlpath}|{appid}|{timestamp}|{sn}说明:
- urlpath 为接口路径本身,例如 /oauth/token
- appid 为客户端 client_id
- timestamp 必须与报文外层字段一致
- sn 为对称密钥编号;如果接口场景没有编号,则传空字符串参与拼接
只要
urlpath、appid、timestamp、sn 任一项不一致,都会导致解密或认证失败。5. 签名规则
签名原文固定为:
{data}|{timestamp}|{nonce}签名说明:
- data 指加密后的密文字段,不是明文 JSON
- timestamp、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 = true
- der = true
- userId / 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 编码后放入请求体 iv
- data:业务 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"
}