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台站IDnumber
callsign呼号string
dxccDXCC实体string
grid网格string
ituITU区域string
cqCQ区域string
station_location台站位置string
first_qso_date首次QSO时间(毫秒时间戳)number
last_qso_date最后QSO时间(毫秒时间戳)numbernull

QsoList

参数名称参数说明是否必须数据类型
callsign对方呼号truestring
worked我的呼号truestring
date通联时间(时间戳)truenumber
mode模式truestring
band接收波段truestring
band_tx发送波段falsestring
freq接收频率truenumber
freq_tx发送频率falsenumber
rst_rcvd接收RSTtruestring
rst_sent发送RSTtruestring
satellite卫星名称falsestring
propagation传播模式falsestring

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客户端IDtruestring
redirect_uri回调地址,必须与应用登记值完全一致truestring
response_type固定为 codetruestring
scope申请的权限范围,逗号分隔truestring
state客户端生成的随机态值,用于回调校验truestring
code_challengePKCE 挑战值truestring
code_challenge_methodPKCE 方法,支持 S256 / SM3truestring

授权成功后,QCS 将通过 redirect_uri 回跳并附带:

参数名称参数说明数据类型
code授权码string
state原样回传的 statestring

scope取值

scope说明
basic读取基础信息(openid、callsign)
station读取台站列表
qsl读取QSL统计信息
qso导入QSO记录

获取Token

POST /oauth/token

请求说明

  • 外层请求体使用“先 SM4-GCM 加密,再 SM2withSM3 签名”的方式传输
  • code 作为外层明文字段传输
  • data 中的 code 需与外层 code 保持一致

请求参数

参数名称参数说明是否必须数据类型
code授权码(明文)truestring
ivGCM 初始向量,base64;解码后为 12 字节随机值truestring
data加密后的明文请求体,base64truestring
authtagGCM 认证标签,base64truestring
signature签名值truestring
timestamp时间戳(秒)truenumber
nonce随机字符串truestring

##### data(明文解密后)

参数名称参数说明是否必须数据类型
client_id客户端IDtruestring
client_secret客户端密钥truestring
code授权码,必须与外层 code 一致truestring
redirect_uri回调地址,必须与授权时一致truestring
code_verifierPKCE 原始校验串truestring

响应参数

响应体同样使用签名 + SM4-GCM 加密;客户端需先验签,再按同一组参数解密。明文结构如下:

参数名称参数说明数据类型
access_token访问令牌string
refresh_token刷新令牌string
token_type固定为 Bearerstring
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 openidstring
callsign当前用户呼号string

获取QSL统计信息

POST /oauth/qsl_info

scope

qsl

请求头

Authorization: Bearer <access_token>

请求说明

  • 响应体使用签名 + SM4-GCM 加密

响应参数(明文解密后)

参数名称参数说明数据类型
worked主呼号string
qso_totalQSO总数number
qsl_totalQSL总数number
latest_qsl_hash最新QSL哈希stringnull
latest_qsl_date最新QSL时间,毫秒时间戳numbernull

获取台站列表(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)truestring
data加密后的密文(base64)truestringdata
authtagGCM认证标签(base64)truestring
signature签名值truestring
timestamp时间戳(秒)truenumber
nonce随机字符串truestring

##### data(明文解密后)

参数名称参数说明是否必须数据类型schema
station_id台站idtruenumber
qso_listqso列表trueQsoList[]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. 字段说明

字段说明
ivBase64 字符串,解码后必须为 12 字节随机值
data业务明文经 SM4-GCM 加密后的密文,Base64 编码
authtagSM4-GCM 认证标签,Base64 编码
signature对 datatimestampnonce 进行 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

请求参数

参数名称参数说明是否必须数据类型
tokenQCS 个人中心获取的 PATtruestring

请求说明

  • 该接口不需要 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
tokenQCS 个人中心获取的 PATtruestring
iv加密向量(16字节随机IV转 base64)truestring
data加密数据(json字符串密文)truestringdata

请求说明

  • 该接口不需要 OAuth access token
  • data 使用 SM4-CBC + PKCS#7 加密
  • 解密密钥直接使用 PAT 原文
  • iv 为 16 字节随机值的 Base64 表示

##### data

参数名称参数说明是否必须数据类型schema
station_id台站idtruenumber
qso_listqso列表trueQsoList[]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"
}