Read your contacts, confirmations and statistics from your own scripts, logging tools and dashboards. A clean JSON API with ADIF export, for QSL World Pro members.
Every contact with band, mode, grid, park and summit references, and QSL status from LoTW, eQSL, QRZ and QSL World cards.
One request gives a standards-compliant ADIF file, filtered by date, band, mode or station.
Totals, confirmations, DXCC entities, and counts by band and mode for your own award trackers.
A key reaches only its owner's logbook. Other stations' email and postal addresses are never part of the API.
The API is for registered developers. Anyone can read this documentation; to get API keys:
Keys work while the Pro license is active. If it lapses, requests answer 403 membership_required until it is renewed; the keys themselves are kept.
Every request goes to https://qslworld.com/api/v1 with your key in the Authorization header.
curl -H "Authorization: Bearer qslw_YOUR_KEY" \
"https://qslworld.com/api/v1/qsos?per_page=5"
The answer is JSON. Lists come back as data plus meta for paging:
{
"data": [
{ "id": 912345, "call": "EA1AHP", "qso_date": "2026-10-01", "time_on": "18:42:15",
"band": "20m", "freq": 14.076, "mode": "FT8", "rst_sent": "-12", "rst_rcvd": "-08",
"gridsquare": "IN53", "country": "Spain", "dxcc": 281,
"qsl": { "confirmed": true, "lotw_rcvd": true, ... }, ... }
],
"meta": { "total": 7880, "page": 1, "per_page": 5, "pages": 1576, "has_more": true }
}
Send the key as a bearer token in the Authorization header: Authorization: Bearer qslw_β¦. Keys are 45 characters and start with qslw_. Never put a key in the URL.
GET /ping needs no key and is handy for checking connectivity.
Each key may make 60 requests a minute and 5,000 a day (UTC). Every response carries the counters:
X-RateLimit-Limit-Minute: 60
X-RateLimit-Remaining-Minute: 57
X-RateLimit-Limit-Day: 5000
X-RateLimit-Remaining-Day: 4921
Over the limit the answer is 429 rate_limited with a Retry-After header in seconds. To read a whole logbook, prefer /export.adi or pages of 100 over many small requests.
Errors use HTTP status codes and a JSON body with a stable code you can test for:
{ "error": { "code": "invalid_key", "message": "That API key is not recognised." } }
| Status | code | Meaning |
|---|---|---|
| 400 | bad_parameter | A query parameter is malformed; the message says which. |
| 401 | missing_key, invalid_key, revoked_key | No key, an unknown key, or a revoked one. |
| 403 | membership_required | The developer account has no active QSL World Pro license. |
| 403 | developer_suspended | The developer account was suspended; contact support. |
| 404 | not_found | No such endpoint, or no such contact in your logbook. |
| 405 | method_not_allowed | v1 is read-only: only GET. |
| 409 | no_logbook | Your account has no logbook yet. |
| 429 | rate_limited | Slow down; see Retry-After. |
| 500 / 503 | server_error, unavailable | Our side. Retry later with back-off. |
/pingNo key needed. Returns {"data":{"ok":true,"version":"1.0",β¦}}.
/meThe callsign the key belongs to, the key's name and scope, and your remaining limits.
/qsosYour contacts, newest first, paged. All parameters are optional and combine:
| Parameter | Example | Meaning |
|---|---|---|
page | 2 | Page number, from 1. |
per_page | 100 | Rows per page, 1–100 (default 25). |
order | asc | desc (default, newest first) or asc. |
from, to | 2026-01-01 | QSO date range, inclusive, UTC. |
call | EA1AHP | Exact callsign worked. |
band | 20m | Exact band. |
mode | FT8 | Exact mode. |
country, state | Spain | Exact country name or state. |
gridsquare | FN | Grid prefix: FN matches FN42, FN31pr… |
confirmed | true | true or false: confirmed by LoTW, QRZ or a QSL World card. |
logbook_id | 3 | Only one of your logbooks. |
/qsos/{id}One contact by its id.
/statsTotals for your logbook: qsos, confirmed, countries, dxcc_entities, first_qso, last_qso, plus by_band and by_mode counts.
/export.adiYour contacts as an ADIF 3.1.4 file, oldest first. Takes the same filters as /qsos. Up to 100,000 contacts per request; use from/to for more.
Field names follow ADIF, in lower case. Empty fields are null. Dates are YYYY-MM-DD and times HH:MM:SS, both UTC; freq is in MHz.
| Field | Type | Notes |
|---|---|---|
id | integer | Stable id of the contact in QSL World. |
call | string | The station worked. |
qso_date, time_on, qso_date_off, time_off | string | UTC. |
band, band_rx, freq, freq_rx | string / number | Bands like 20m; frequencies in MHz. |
mode, submode | string | ADIF modes, e.g. FT8, MFSK/FT4. |
rst_sent, rst_rcvd | string | |
name, qth, state, cnty, country, dxcc, cont, cqz, ituz, pfx, gridsquare | The other station, as logged. No email or street address. | |
tx_pwr, prop_mode, sat_name, sat_mode | ||
iota, sota_ref, pota_ref, wwff_ref, sig, sig_info | string | The other station's references. |
my_gridsquare, my_iota, my_sota_ref, my_pota_ref, my_wwff_ref, my_sig, my_sig_info | string | Your references for the contact. |
station_callsign, operator | string | The callsign you used and who operated. |
contest_id, stx, srx, stx_string, srx_string | Contest exchange. | |
comment | string | |
qsl | object | confirmed (bool), lotw_sent, lotw_rcvd, lotw_date, eqsl_sent, eqsl_rcvd, qrz_rcvd, qrz_date, card_sent, card_rcvd. |
logbook_id | integer | Which of your logbooks it is in. |
created_at | string | When it reached QSL World, ISO 8601. |
import requests
KEY = "qslw_YOUR_KEY"
url = "https://qslworld.com/api/v1/qsos"
page = 1
while True:
r = requests.get(url, params={"page": page, "per_page": 100},
headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
r.raise_for_status()
body = r.json()
for q in body["data"]:
print(q["qso_date"], q["time_on"], q["call"], q["band"], q["mode"])
if not body["meta"]["has_more"]:
break
page += 1
const KEY = process.env.QSLW_KEY;
const u = new URL("https://qslworld.com/api/v1/qsos");
u.search = new URLSearchParams({ band: "20m", confirmed: "true", from: "2026-01-01", per_page: "100" });
const r = await fetch(u, { headers: { Authorization: `Bearer ${KEY}` } });
const { data, meta } = await r.json();
console.log(`${meta.total} confirmed on 20m`, data.map(q => q.call));
curl -H "Authorization: Bearer qslw_YOUR_KEY" \
-o september.adi \
"https://qslworld.com/api/v1/export.adi?from=2026-09-01&to=2026-09-30"
1.0 · October 2026 · First release: /ping, /me, /qsos, /qsos/{id}, /stats, /export.adi. Read-only. Writing contacts and ADIF import are planned for a later version.
Version 2026-10. These sit alongside the QSL World Terms of Use and Privacy Policy.
/api/v1 working while it is current.