API v2

API v2

目录

警告:需要 Wavelog 3.1.0 或更高版本

API v2 仅在 Wavelog 3.1.0 及更高版本中可用。如果你的实例运行的是更早的版本,请先升级。

Wavelog 的 REST API v2 是一个现代化的、面向资源的 HTTP API,面向第三方工具——电台、外部日志软件、监控系统以及你自己的脚本。它与旧版 v1 API 并行运行:v2 并不取代 v1,两者可以同时使用。

注意

API v2 需要专用的 v2 令牌(前缀为 wl2_)。旧版 v1 API 密钥在任何 /api/v2/… 端点上均不会被接受。

与 v1 有何不同

方面 v1(/api/…) v2(/api/v2/…)
URL 风格 RPC——操作即方法名(/api/qso) REST——资源路径加 HTTP 动词(POST /api/v2/qso)
HTTP 动词 大多为 POST GET、POST、PATCH、DELETE
认证 密钥放在 JSON 请求体 / URL 中 Authorization: Bearer <token> 请求头
令牌格式 wl… wl2_…
令牌存储 数据库中明文保存 仅保存 SHA-256 哈希
权限 单一的 r / rw 权限 按资源划分的细粒度作用域(qso:read、qso:write、…)
响应 每个端点各不相同 统一的信封结构,语义由 HTTP 状态码承载

基础 URL

每个端点都位于 api/v2 前缀之下:

https://<your-wavelog-host>/index.php/api/v2/<resource>[/<id>]
  • <resource> 是单数、小写的名称(qso、station、logbook、radio、statistic、confirmation、lookup、club、token)。
  • <id> 在适用情况下是单个条目的数字主键。

这就是完整的 URL 空间:包含超过这两段的路径并不存在,会返回 404 not_found,而不是忽略多余部分给出应答。结尾加一个斜杠是可以的。

/api/v2/qso          ✅ list
/api/v2/qso/42       ✅ single item
/api/v2/qso/         ✅ same as /api/v2/qso
/api/v2/qso/42/notes ❌ 404 not_found

取决于你的 Web 服务器配置,你或许可以省略 index.php/ 这一段。本文档中的所有示例都包含它,以获得最大的兼容性。

可用资源

资源 路径 作用域 说明
QSO /api/v2/qso qso:* 读取、创建、更新和删除日志通联;ADIF 导入/导出
Station /api/v2/station station:* 管理台站位置(日志配置)
Logbook /api/v2/logbook logbook:* 将台站位置归入日志
Radio /api/v2/radio radio:* 推送并读取实时 CAT 电台状态
Statistic /api/v2/statistic statistic:read 供仪表盘与监控使用的只读计数器
Confirmation /api/v2/confirmation confirmation:read 列出每个 QSO 的 QSL 确认(LoTW、eQSL、QSL 卡、QRZ、Clublog)
Contest /api/v2/contest contest:* 管理比赛会话及其 QSO 关联
Lookup /api/v2/lookup lookup:read 查询呼号(DXCC + 已通联/已确认)或网格
Club /api/v2/club club:read 列出集体台的成员(仅管理员)
Catalog /api/v2/catalog — 实例范围的参考数据(比赛、DXCC 实体、分区)
Token /api/v2/token — 当前令牌的元数据(whoami)

认证

创建令牌

  1. 登录 Wavelog,从用户菜单打开「API」区域。
  2. 新建一个 API v2 令牌。你需要选择:
    • 一个名称(便于日后识别该令牌),
    • 一个或多个作用域(见 作用域),
    • 一个有效期:30、90 或 365 天,或永不过期。
  3. 完整令牌(wl2_…)只会显示一次,就在创建之后。请立即复制——Wavelog 只保存哈希,之后永远无法再次显示。

你可以随时在同一界面吊销令牌;吊销立即生效。

警告

请像对待密码一样对待令牌。任何持有它的人都能在授予的作用域内操作你的数据。如果令牌泄露,请吊销它并创建新的令牌。

发送令牌

使用 Bearer 方案在 Authorization 请求头中发送令牌:

Authorization: Bearer wl2_your_token_here

作为备选,也接受 X-API-Key 请求头:

X-API-Key: wl2_your_token_here

注意

API 是无会话的。它从不使用你的浏览器登录——所有访问权限与数据归属都来自令牌的所有者。令牌只能查看和修改创建它的用户所拥有的数据。

在切换到某个集体台后创建的令牌归该集体台所有,它能做什么还取决于创建者在那里的权限级别。参见集体台。

作用域

权限是细粒度的,遵循 <resource>:<action> 模式:

动作 授予的操作 需要它的请求
read 读取数据 GET 请求
write 创建和更新数据 POST、PATCH 请求
delete 删除数据 DELETE 请求

各作用域相互独立,因此你可以签发只读令牌、只写令牌,或任意组合。如果某个请求的动词需要令牌未携带的作用域,该请求会被拒绝并返回 403 insufficient_scope。

创建令牌时提供的作用域全集如下:

作用域 允许的操作
qso:read / qso:write / qso:delete 读取 / 创建+更新 / 删除 QSO
station:read / station:write / station:delete 读取 / 创建+更新 / 删除台站位置
logbook:read / logbook:write / logbook:delete 读取 / 创建+更新 / 删除日志
radio:read / radio:write / radio:delete 读取 / 创建+更新 / 删除电台
statistic:read 读取统计
confirmation:read 读取 QSL 确认
contest:read / contest:write / contest:delete 读取 / 创建+更新 / 删除比赛会话
lookup:read 查询呼号与网格
club:read 读取集体台成员

有两个资源完全不需要作用域——任何有效令牌都可以读取它们:Token(whoami,即其自身的元数据)和 Catalog,后者提供的是实例范围的参考数据而非用户数据。

对于个人令牌,作用域是唯一的权限层。集体台令牌还会在其之上通过第二层校验:成员在该集体台的权限级别。如果令牌背后的级别低于管理员,持有 station:write 也无济于事。

请求与响应约定

HTTP 动词

动词 路径 含义
GET /api/v2/<resource> 列出条目
GET /api/v2/<resource>/<id> 获取单个条目
POST /api/v2/<resource> 创建条目
PATCH /api/v2/<resource>/<id> 部分更新——只修改你发送的字段
DELETE /api/v2/<resource>/<id> 删除条目

注意(API 没有 PUT)

更新永远是部分的。Wavelog 是你的数据的权威来源,而整体替换会让客户端把它根本不知道存在的字段清空——未来版本新增的每个字段都会被旧客户端悄悄抹掉。要完全覆盖某个条目,请在 PATCH 中显式发送每一个字段。

并非每个资源都实现所有动词(例如 Radio 没有 PATCH,Statistic 是只读的)。不支持的动词会返回 405 method_not_allowed,并附带 Allow 响应头,准确列出该资源确实实现的动词:

HTTP/1.1 405 Method Not Allowed
Allow: GET, POST, PATCH, DELETE

动词的检查先于作用域,因此向只读资源发送 POST 得到的是 405 而不是 403:它所需的作用域(lookup:write)并不存在,任何令牌都不可能持有它,所以报告缺少作用域会指向错误的问题。

请求体

  • 以 Content-Type: application/json 发送 JSON 格式的写入载荷。
  • 请求体字段必须是标量值(字符串、数字、布尔值或 null)。嵌套对象或数组会被拒绝并返回 400 validation_error。
  • 格式错误的 JSON 返回 400 invalid_json。

成功响应

成功的响应使用简洁的信封结构。载荷始终位于 data 之下。每个 JSON 成功响应还包含一个 meta 块,给出通用的请求上下文:

字段 含义
timestamp UTC 响应时间戳(ISO-8601,例如 2026-07-18T12:34:56+00:00)
resource API v2 的资源段(qso、station、statistic、…;/api/v2 和 /api/v2/status 为 meta)
method HTTP 方法(GET、POST、PATCH、DELETE)

资源特有的元数据(分页、查询详情、统计概况、…)会添加到同一个 meta 对象中。

示例:

{
  "data": { "id": 42, "call": "N9EAT" },
  "meta": {
    "timestamp": "2026-07-18T12:34:56+00:00",
    "resource": "qso",
    "method": "GET"
  }
}
{
  "data": [ { "id": 42 }, { "id": 43 } ],
  "meta": {
    "timestamp": "2026-07-18T12:34:56+00:00",
    "resource": "qso",
    "method": "GET",
    "page": 1,
    "per_page": 50,
    "count": 2
  }
}

成功的 DELETE 返回 204 No Content,响应体为空。

错误响应

错误使用 error 信封,并配合相应的 HTTP 状态码:

{
  "error": {
    "code": "insufficient_scope",
    "message": "Token is missing the required scope: qso:write",
    "details": { "required_scope": "qso:write" }
  }
}
HTTP code 何时出现
400 validation_error 字段缺失或无效,或值为非标量
400 invalid_json 请求体不是合法 JSON
401 unauthorized 未提供令牌
401 invalid_token 未知、已吊销或非 wl2_ 令牌(例如旧版 v1 密钥)
401 token_expired 令牌的有效期已过
403 insufficient_scope 令牌缺少该动词所需的作用域
403 forbidden 所引用的条目不属于令牌所有者
403 insufficient_club_permission 集体台令牌的权限级别过低(见 details.required_level)
403 club_access_revoked 令牌背后的集体台成员资格已被撤销
404 not_found 未知资源、路径或条目 id——见下方说明
405 method_not_allowed 该资源不支持此动词(见 Allow 响应头)
409 conflict 重复,或当前状态不允许该操作
429 rate_limited 超过速率限制(见 details.retry_after,单位为秒)
500 internal_error 意外的服务器端错误

注意(404 也涵盖你看不到的条目)

存在但对你的令牌不可及的条目会报告为 404 not_found,而不是 403。确认某一行存在本身就已经泄露了关于其他用户数据的某些信息。这就是为什么集体台成员对另一位操作员记录的 QSO 会得到 404。

分页

可能返回大量行的列表端点(目前是 QSO)通过查询参数支持分页:

参数 默认值 说明
page 1 从 1 开始的页码
per_page 50 最大 5000(各格式的默认值见 QSO 资源)

分页字段是 meta 的一部分,用于报告分页状态:

字段 含义
page 你请求的页码
per_page 每页条目数
count 本页的条目数
total 当前筛选条件下所有页面的条目总数
total_pages ceil(total / per_page)
has_more 当 page < total_pages 时为 true
{
  "data": [ { "id": 42 } ],
  "meta": {
    "timestamp": "2026-07-18T12:34:56+00:00",
    "resource": "qso",
    "method": "GET",
    "page": 1,
    "per_page": 50,
    "count": 50,
    "total": 237,
    "total_pages": 5,
    "has_more": true
  }
}

要遍历全集,请逐页请求,直到 has_more 为 false(等价地,直到 page 达到 total_pages)。你永远不需要试探是否存在空页。

速率限制

速率限制是按需启用的:在实例所有者配置它之前并不可用,并且使用滑动窗口。超过限制时,API 返回 429 rate_limited,并附带 details.retry_after(需要等待的秒数)。如果未配置任何限制,则不做限流——但仍请只在你确实有新数据要发送或获取时才调用 API。

限流桶

每个 v2 请求最多经过两个相互独立的计数器:

限流桶 检查时机 计数单位 用途
api_v2_auth 认证之前 客户端 IP 地址 减缓对令牌密钥空间的暴力破解尝试
api_v2_<resource> 认证成功之后 令牌 常规使用限制,例如 api_v2_qso、api_v2_station

持有有效令牌的客户端会通过两者。持有无效令牌的客户端永远过不了第一关——这正是关键:被拒绝的令牌永远到不了按令牌计数的桶,因此如果没有 api_v2_auth,令牌密钥空间就可能被全速探测。

api_v2_auth 故意不按资源划分。否则只要轮换 URL 中的资源(/qso、/station、/radio、…)就能绕过限制。

配置

限制项位于 application/config/config.php(Docker 安装为 application/config/docker/config.php)。每个条目设置在 window 秒内的最大 requests 数:

$config['api_rate_limits'] = [
    // API v2 — bucket names carry the "api_v2_" prefix
    'api_v2_auth'      => ['requests' => 10,  'window' => 120],
    'api_v2_qso'       => ['requests' => 120, 'window' => 60],
    'api_v2_lookup'    => ['requests' => 60,  'window' => 60],
    'api_v2_statistic' => ['requests' => 30,  'window' => 60],

    // v1 endpoints keep their bare method names
    'private_lookup'   => ['requests' => 60,  'window' => 60],

    // applies to every bucket not listed above
    'default'          => ['requests' => 60,  'window' => 60],
];

警告:没有 default 时,未列出的限流桶不受限制

限流桶按精确名称查找,然后回退到 default。如果两者都不匹配,该限流桶就完全不施加任何限制。

这一点很容易搞错:只配置了 'private_lookup' 的实例虽然开启了速率限制,但每一个 v2 端点——包括 api_v2_auth——仍然不受限流。

要么设置一个 default,要么显式列出 api_v2_auth。

v1 与 v2 共用同一个配置数组,但从不冲突:v1 的限流桶以 API 方法命名(lookup、private_lookup、qso),v2 的限流桶始终带 api_v2_ 前缀。两者独立配置。

要完全关闭速率限制,请设置 $config['api_rate_limits'] = null; 或让它保持注释状态。

隐藏的用户数据

描述他人账户的端点会披露个人数据,而不同国家允许实例交出的内容各不相同。因此由实例所有者挑选要隐藏哪些字段:

$config['apiv2_hide_userdata'] = ['user_email', 'user_locator'];

列出的字段会被完全排除在响应之外——它们不会被置空、不是 null,只是键不存在。空数组(默认值)表示披露全部内容。

目前这会影响 Club 资源,它是唯一返回其他用户账户数据的资源。

始终返回且无法隐藏的字段是:

user_id
user_callsign
permission_level

可以隐藏的字段是:

user_firstname
user_lastname
user_locator
user_name
user_email
user_language

警告(致客户端):请以防御性方式处理可选字段

你无法从外部判断某个实例配置了披露哪些字段,也没有任何端点会报告这一点。请在使用某个字段之前先确认它存在,不要构建只有在实例恰好交出电子邮件地址时才有效的功能。

CORS

API 以 204 No Content 应答 OPTIONS 预检请求,并对 GET、POST、PATCH、DELETE 和 OPTIONS 方法允许跨域调用(Access-Control-Allow-Origin: *),允许的请求头为 Authorization、Content-Type 和 X-API-Key。

状态端点

GET /api/v2(以及 GET /api/v2/status)是公开的——无需令牌——很适合用来快速检查可达性:

curl https://<WAVELOG_URL>/index.php/api/v2/status
{
  "data": { "name": "Wavelog API", "status": "ok" },
  "meta": {
    "timestamp": "2026-07-18T12:34:56+00:00",
    "resource": "status",
    "method": "GET"
  }
}

快速开始

列出你的台站位置:

curl https://<WAVELOG_URL>/index.php/api/v2/station \
     -H "Authorization: Bearer wl2_your_token_here"

在台站位置 1 上创建一次 QSO:

curl -X POST https://<WAVELOG_URL>/index.php/api/v2/qso \
     -H "Authorization: Bearer wl2_your_token_here" \
     -H "Content-Type: application/json" \
     -d '{
           "station_profile_id": 1,
           "call": "N9EAT",
           "band": "20m",
           "mode": "SSB",
           "freq": 14075000,
           "qso_date": "2026-06-16",
           "time_on": "1706",
           "rst_sent": "59",
           "rst_rcvd": "57"
         }'

完整的字段参考见各资源页面: QSO · Station · Logbook · Radio · Statistic · Contest · Lookup · Club · Catalog · Token。

如果你的令牌属于某个集体台而不是你自己的账户,也请阅读集体台——权限级别会改变其中若干资源返回的内容。

Wavelog 手册(中文版) / API v2 0 字 0 行 bg8lng
2026-10-21T12:42:36.295376625Z 2026-10-21T12:43:57.620794950Z