比赛

比赛(Contest)

目录

比赛资源管理比赛会话——即比赛模块把 QSO 归组为一次比赛过程的方式——以及会话与其 QSO 之间的关联。它是 Web 界面中比赛页面的 API 对应物,让外部工具(日志软件、离线镜像、同步客户端)能够读取并复制比赛会话,而不必通过导入历史比赛手工重建。

  • 基础路径: /api/v2/contest
  • 作用域: contest:read、contest:write、contest:delete
  • 起始版本: 3.2.0

注意

请先阅读 API v2 概览,了解认证、响应封装与错误码。

端点

动词 路径 作用域 用途 起始版本
GET /api/v2/contest contest:read 列出令牌所有者的比赛会话 3.2.0
GET /api/v2/contest/{id} contest:read 单个会话,包括其关联的 QSO id 3.2.0
POST /api/v2/contest contest:write 创建会话,可选地关联 QSO 3.2.0
PATCH /api/v2/contest/{id} contest:write 部分更新;关联与取消关联 QSO 3.2.0
DELETE /api/v2/contest/{id} contest:delete 删除会话 3.2.0

属于其他用户的会话返回 404 not_found,绝不会返回 403——API 从不确认外部 id 是否存在。

会话对象

{
  "id": 17,
  "contest": "DARC-WAG",
  "contest_name": "Worked All Germany Contest",
  "time_start": "2026-10-17 15:00:00",
  "time_end": "2026-10-18 15:00:00",
  "station_id": 3,
  "comment": "SO ABHP",
  "settings": {
    "exchangetype": "Serialexchange",
    "copyexchangeto": "dok",
    "exchangefields": ["serial", "exchange"],
    "callbook_lookup": true,
    "custom_name": "",
    "serial_per_band": false,
    "serial_scope": "station"
  },
  "qso_count": 412,
  "created_at": "2026-10-17 14:52:11",
  "updated_at": "2026-10-18 15:04:37"
}
字段 含义
id 会话 id,在本 Wavelog 实例内保持稳定
contest ADIF 比赛名称,例如 DARC-WAG——用于定位一场比赛的取值,见比赛名称
contest_name 来自目录的可读比赛名称
time_start / time_end 会话时间窗口,YYYY-MM-DD HH:MM:SS
station_id 会话所属的台站位置
comment 自由文本备注
settings 会话设置,见下文
qso_count 当前关联到该会话的 QSO 数量
created_at / updated_at 由 Wavelog 维护的时间戳

提示:用 ADIF 名称定位比赛,而不是 id

数字形式的比赛目录 id 是实例本地的——id 42 在另一个 Wavelog 上可能是另一场比赛。写请求接受 contest_id 作为替代,但在实例之间同步的客户端应始终使用 contest。响应中同时带有 ADIF 名称和解析出的目录名称。

可用的比赛由 GET /api/v2/catalog?topic=contest 提供——无需任何作用域,因此任何有效令牌都能填充比赛下拉菜单。

比赛名称

同一个取值有三种不同的叫法,很容易搞混:

位置 叫法
本 API contest
Web 界面,比赛 → 比赛管理 ADIF 名称(编辑表单中为 比赛 ADIF 名称)
导出的 ADIF 记录 CONTEST_ID

因此,管理员以 ADIF 名称 = DARC-WAG 创建的比赛,在这里通过 "contest": "DARC-WAG" 定位;从此类会话导出的每条 QSO 都带有 <CONTEST_ID:8>DARC-WAG。

警告:CONTEST_ID 不是 contest_id

CONTEST_ID 是该文本取值的 ADIF 字段名。本 API 的 contest_id 完全是另一回事:数字形式的目录 id。发送 {"contest_id": "DARC-WAG"} 会以 400 validation_error 失败,{"contest": 57} 同样如此。

会话设置

settings 是一个 JSON 对象。读取时它总是完整返回,并在模块默认值之上合并,因此客户端永远不必知道存储了哪些键。写入时只校验你发送的键,并将其合并到现有取值之上。

键 默认值 取值
exchangefields ["serial"] serial、gridsquare、exchange 的非空数组
exchangetype Serial 派生字段,见下文
copyexchangeto "" ""、dok、locator、qth、name、age、state、power
callbook_lookup true 布尔值
custom_name "" 字符串
serial_per_band false 布尔值
serial_scope "station" station 或 operator

exchangefields 是数组,因此这里是 API 通常的仅标量请求体规则唯一不适用之处——settings、qso_ids、link_qso_ids 和 unlink_qso_ids 可以嵌套,其他所有字段必须保持标量。

exchangetype 是派生的历史遗留字段。你发送的任何值都会被忽略,并由 exchangefields 重新计算,与 Web 界面的做法完全一致,因此两者永远不会偏离:

exchangefields 生成的 exchangetype
["serial"] Serial
["exchange"] Exchange
["serial", "exchange"] Serialexchange
["serial", "gridsquare"] Serialgridsquare
["exchange", "gridsquare"] Exchangegridsquare
["serial", "gridsquare", "exchange"] SerialGridExchange

未知的设置键或无效取值会返回 400 validation_error,各条消息位于 error.details.errors 中。

列出会话

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

会话按最新优先返回(先按 time_start,再按 id)。该列表不分页,也不包含 qso_ids——需要时请获取单个会话。

参数 默认值 含义
station_id 令牌用户拥有的全部台站 逗号分隔的台站 id;会做归属校验
since_id 0 仅返回 id 大于此值的会话——用于增量同步

令牌不拥有的 station_id 会以 403 forbidden 拒绝,而不是静默丢弃。

获取单个会话

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

响应是会话对象再加上 qso_ids,即已关联 QSO 主键的排序列表——与 QSO 资源使用的 id 相同,因此客户端可以镜像该关联关系:

{
  "data": {
    "id": 17,
    "contest": "DARC-WAG",
    "qso_count": 3,
    "qso_ids": [48190, 48200, 48215]
  }
}

创建会话

curl -X POST "https://<WAVELOG_URL>/index.php/api/v2/contest" \
     -H "Authorization: Bearer wl2_your_token_here" \
     -H "Content-Type: application/json" \
     -d '{
           "contest": "DARC-WAG",
           "time_start": "2026-10-17 15:00",
           "time_end": "2026-10-18 15:00",
           "station_id": 3,
           "comment": "SO ABHP",
           "settings": {
             "exchangefields": ["serial", "exchange"],
             "copyexchangeto": "dok"
           }
         }'
字段 必填 说明
contest 或 contest_id 是 ADIF 比赛名称(推荐)或目录 id
time_start 是 YYYY-MM-DD HH:MM 或 YYYY-MM-DD HH:MM:SS
time_end 是 相同格式
station_id 是 必须属于令牌所有者
comment 否 默认为空
settings 否 在默认值之上合并
qso_ids 否 立即关联的 QSO,见关联 QSO

成功时返回 201 Created,带有指向新会话的 Location 响应头,并在 data 中返回创建好的会话。

比赛必须存在于比赛目录中且处于启用状态;未知或已停用的比赛返回 400 validation_error。目录资源列出了可接受的取值。qso_ids 列表会在会话创建之前校验,因此被拒绝的关联列表绝不会留下一个废弃的空会话。

更新会话

curl -X PATCH "https://<WAVELOG_URL>/index.php/api/v2/contest/17" \
     -H "Authorization: Bearer wl2_your_token_here" \
     -H "Content-Type: application/json" \
     -d '{"time_end": "2026-10-18 16:00", "settings": {"serial_per_band": true}}'

只有请求体中出现的字段会改变。settings 会逐键合并到已存储的设置之上——发送 {"serial_per_band": true} 不会改动其他任何设置。既无可编辑字段又无关联列表的请求体会返回 400 validation_error。

关联与取消关联 QSO

URL 空间没有子路径,因此关联关系放在 PATCH 的请求体中传递:

curl -X PATCH "https://<WAVELOG_URL>/index.php/api/v2/contest/17" \
     -H "Authorization: Bearer wl2_your_token_here" \
     -H "Content-Type: application/json" \
     -d '{"link_qso_ids": [48190, 48200], "unlink_qso_ids": [47001]}'

响应是会话对象并附加操作结果——仅当请求中确实包含关联列表时才会出现这些额外字段:

{
  "data": {
    "id": 17,
    "qso_count": 413,
    "linked": 2,
    "skipped": [],
    "unlinked": 1
  }
}
字段 含义
linked 新关联到该会话的 QSO 数量
skipped 已关联到其他会话因而未作改动的 id
unlinked 已移除的关联数量

关联是幂等的:已关联到本会话的 id 会被静默忽略,因此同步客户端可以重发完整列表而无需特殊处理。关联到其他会话的 id 永远不会被抢占——它会落入 skipped。

关联还会把 QSO 的 CONTEST_ID 字段设为该会话的比赛,取消关联时再次清除它(仅针对确实关联到本会话的 QSO),与高级日志的挂接/解除挂接流程一致。

不属于令牌所有者的 QSO id 会返回 403 forbidden,违规的 id 位于 error.details.qso_ids 中;整个列表会在应用任何改动之前完成校验,因此被拒绝的请求不会改变任何内容。

删除会话

curl -X DELETE "https://<WAVELOG_URL>/index.php/api/v2/contest/17" \
     -H "Authorization: Bearer wl2_your_token_here"

返回 204 No Content。默认情况下,已关联的 QSO 保留在日志中,只是失去会话关联——与在 Web 界面中取消勾选复选框相同。

若要彻底拆除整个比赛过程(包括 QSO):

curl -X DELETE "https://<WAVELOG_URL>/index.php/api/v2/contest/17?delete_qsos=true" \
     -H "Authorization: Bearer wl2_your_token_here"

警告:delete_qsos=true 还需要 qso:delete

删除日志中的通联属于 QSO 资源的权限。仅持有 contest:delete 的令牌会得到 403 insufficient_scope,且 details.required_scope = "qso:delete",并且不会删除任何内容。已删除的 QSO 就此消失——无法撤销。

集体台令牌

对于集体台令牌,成员的权限级别会在作用域之上额外生效,与 Web 界面允许的行为一致:

操作 所需级别
列出与读取会话 任意成员
创建、更新或删除会话 9(集体台干事)
关联与取消关联 QSO 任意成员——但仅限自己的 QSO

低于干事级别的成员若尝试编辑会话本身,会得到 403 insufficient_club_permission。会话是集体台共享的基础设施,因此其字段仅限干事修改,而把自己的 QSO 挂接到运行中的会话则不然——这与高级日志采用的分工相同。

错误

状态码 代码 场景
400 validation_error 字段缺失或格式错误、比赛未知/未启用、设置无效、更新为空
403 forbidden 该令牌无权访问的 station_id 或 QSO id
403 insufficient_scope 缺少 contest:* 作用域,或 delete_qsos=true 时缺少 qso:delete
403 insufficient_club_permission 低于干事级别的集体台成员编辑会话
404 not_found 该令牌所有者不存在此会话
Wavelog 手册(中文版) / 比赛 0 字 0 行 bg8lng
2026-10-21T12:42:36.366658904Z 2026-10-21T12:44:02.550454321Z