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 编码后放入 iv
- authtag:GCM 输出的认证标签,Base64 编码后放入 authtag
- AAD 固定为:

{urlpath}|{appid}|{timestamp}|{sn}


说明:

- urlpath 为接口路径本身,例如 /oauth/token
- appid 为客户端 client_id
- timestamp 必须与报文外层字段一致
- sn 为对称密钥编号;如果接口场景没有编号,则传空字符串参与拼接

只要 urlpathappidtimestampsn 任一项不一致,都会导致解密或认证失败。

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"
}