A working reference implementation showing how to integrate the DxQSO Ham Logbook Sync™ API into your ham radio application. Upload QSOs, download logbooks, and sync in real time.
Authorization: Bearer <vendor_token>dxqso-station-token: Bearer <station_token>Authorization: Bearer <vendor_token> — identifies your application (one per vendor, long-lived)dxqso-station-token: Bearer <station_token> — identifies the end-user's station (JWT, generated per user)
https://api.dxqso.net/logbook/station/station_info/
https://api.dxqso.net/upload
call — Callsign of the contacted station (e.g. N1EZ)band — Amateur radio band (e.g. 40M, 20M)mode — Operating mode (e.g. FT8, CW, SSB)qso_date — Date in YYYYMMDD format (e.g. 20240802)time_on — UTC time in HHMMSS or HHMM format (e.g. 015115)https://api.dxqso.net/logbook/download
X-Next-Token.// DxQSO API — C# / .NET 6+ example
using System.Net.Http.Headers;
using System.Text.Json;
public class DxqsoClient {
private readonly HttpClient _http;
private readonly string _vendorToken, _stationToken;
private const string ApiBase = "https://api.dxqso.net";
public DxqsoClient(HttpClient http, string vendorToken, string stationToken) {
_http = http; _vendorToken = vendorToken; _stationToken = stationToken;
}
private void AddAuth(HttpRequestMessage req, string? nextToken = null) {
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _vendorToken);
req.Headers.Add("dxqso-station-token", $"Bearer {_stationToken}");
if (nextToken != null) req.Headers.Add("X-Next-Token", nextToken);
}
public async Task UploadQsoAsync(string call, string band, string mode, string date, string timeOn) {
var payload = JsonSerializer.Serialize(new { qsos = new[] { new { call, band, mode, qso_date = date, time_on = timeOn } } });
var req = new HttpRequestMessage(HttpMethod.Post, $"{ApiBase}/upload") {
Content = new StringContent(payload, System.Text.Encoding.UTF8, "application/json")
};
AddAuth(req);
(await _http.SendAsync(req)).EnsureSuccessStatusCode();
}
public async Task<List<string>> DownloadLogbookAsync(string? since = null) {
var results = new List<string>(); string? nextToken = null;
do {
var url = $"{ApiBase}/logbook/download?format=json{(since != null ? $"&qso_since={Uri.EscapeDataString(since)}" : "")}";
var req = new HttpRequestMessage(HttpMethod.Get, url);
AddAuth(req, nextToken);
var res = await _http.SendAsync(req); res.EnsureSuccessStatusCode();
results.Add(await res.Content.ReadAsStringAsync());
res.Headers.TryGetValues("X-Next-Token", out var tokens);
nextToken = tokens?.FirstOrDefault();
} while (nextToken != null);
return results;
}
}// DxQSO API — C++ / libcurl example
#include <curl/curl.h>
#include <nlohmann/json.hpp>
#include <string>
using json = nlohmann::json;
class DxqsoClient {
std::string _vendorToken, _stationToken;
const std::string _apiBase = "https://api.dxqso.net";
static size_t WriteBody(char* p, size_t, size_t n, std::string* out) { out->append(p, n); return n; }
curl_slist* BuildHeaders(const std::string& ct, const std::string& nt = "") {
curl_slist* h = nullptr;
h = curl_slist_append(h, ("Authorization: Bearer " + _vendorToken).c_str());
h = curl_slist_append(h, ("dxqso-station-token: Bearer " + _stationToken).c_str());
h = curl_slist_append(h, ("Content-Type: " + ct).c_str());
if (!nt.empty()) h = curl_slist_append(h, ("X-Next-Token: " + nt).c_str());
return h;
}
public:
DxqsoClient(const std::string& vt, const std::string& st) : _vendorToken(vt), _stationToken(st) {
curl_global_init(CURL_GLOBAL_DEFAULT);
}
~DxqsoClient() { curl_global_cleanup(); }
void UploadQso(const std::string& call, const std::string& band, const std::string& mode,
const std::string& date, const std::string& timeOn) {
json body = { {"qsos", {{ {"call",call},{"band",band},{"mode",mode},{"qso_date",date},{"time_on",timeOn} }}} };
std::string payload = body.dump();
CURL* curl = curl_easy_init(); std::string resp;
curl_slist* hdrs = BuildHeaders("application/json");
curl_easy_setopt(curl, CURLOPT_URL, (_apiBase + "/upload").c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hdrs);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteBody);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_perform(curl);
curl_slist_free_all(hdrs); curl_easy_cleanup(curl);
}
};// DxQSO API — Swift / macOS 12+ example
import Foundation
class DxqsoClient {
private let session = URLSession.shared
private let apiBase = "https://api.dxqso.net"
private let vendorToken: String
private let stationToken: String
init(vendorToken: String, stationToken: String) {
self.vendorToken = vendorToken
self.stationToken = stationToken
}
private func addAuth(to request: inout URLRequest, nextToken: String? = nil) {
request.setValue("Bearer \(vendorToken)", forHTTPHeaderField: "Authorization")
request.setValue("Bearer \(stationToken)", forHTTPHeaderField: "dxqso-station-token")
if let t = nextToken { request.setValue(t, forHTTPHeaderField: "X-Next-Token") }
}
func uploadQso(call: String, band: String, mode: String, date: String, timeOn: String) async throws {
let payload: [String: Any] = ["qsos": [["call": call, "band": band, "mode": mode, "qso_date": date, "time_on": timeOn]]]
var req = URLRequest(url: URL(string: "\(apiBase)/upload")!)
req.httpMethod = "POST"
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONSerialization.data(withJSONObject: payload)
addAuth(to: &req)
let (_, response) = try await session.data(for: req)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw URLError(.badServerResponse) }
}
func downloadLogbook(since: String? = nil) async throws -> [[String: Any]] {
var all: [[String: Any]] = []; var nextToken: String? = nil
repeat {
var comps = URLComponents(string: "\(apiBase)/logbook/download")!
var items = [URLQueryItem(name: "format", value: "json")]
if let s = since { items.append(.init(name: "qso_since", value: s)) }
comps.queryItems = items
var req = URLRequest(url: comps.url!)
addAuth(to: &req, nextToken: nextToken)
let (data, res) = try await session.data(for: req)
if let body = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let page = body["qsos"] as? [[String: Any]] { all.append(contentsOf: page) }
nextToken = (res as? HTTPURLResponse)?.value(forHTTPHeaderField: "X-Next-Token")
} while nextToken != nil
return all
}
}# DxQSO API — Python 3.8+ example
# pip install requests
import requests
from typing import Optional
class DxqsoClient:
API_BASE = "https://api.dxqso.net"
def __init__(self, vendor_token: str, station_token: str):
self._vendor_token = vendor_token
self._station_token = station_token
self._session = requests.Session()
def _auth(self, next_token: Optional[str] = None) -> dict:
h = {"Authorization": f"Bearer {self._vendor_token}",
"dxqso-station-token": f"Bearer {self._station_token}"}
if next_token: h["X-Next-Token"] = next_token
return h
def upload_qso(self, call, band, mode, qso_date, time_on, freq=None):
qso = {"call": call, "band": band, "mode": mode, "qso_date": qso_date, "time_on": time_on}
if freq: qso["freq"] = freq
self._session.post(f"{self.API_BASE}/upload", headers=self._auth(), json={"qsos": [qso]}).raise_for_status()
def upload_adif_file(self, file_path: str):
with open(file_path, "r", encoding="utf-8") as f: content = f.read()
self._session.post(f"{self.API_BASE}/upload", headers={**self._auth(), "Content-Type": "text/plain"}, data=content.encode()).raise_for_status()
def download_logbook(self, since=None, mode=None, band=None):
all_qsos, next_token = [], None
while True:
params = {"format": "json"}
if since: params["qso_since"] = since
if mode: params["qso_mode"] = mode
if band: params["qso_band"] = band
r = self._session.get(f"{self.API_BASE}/logbook/download", headers=self._auth(next_token), params=params)
r.raise_for_status()
all_qsos += r.json().get("qsos", [])
next_token = r.headers.get("X-Next-Token")
if not next_token: break
return all_qsos# DxQSO API — cURL examples
VENDOR="your_vendor_token"
STATION="your_station_token"
# Upload a single QSO
curl -s -X POST https://api.dxqso.net/upload \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION" \
-H "Content-Type: application/json" \
-d '{"qsos": [{"call":"VK2ABC","band":"40m","mode":"FT8","qso_date":"20240802","time_on":"015115"}]}'
# Upload an ADIF file
curl -s -X POST https://api.dxqso.net/upload \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION" \
-H "Content-Type: text/plain" \
--data-binary @my_log.adi
# Download as JSON
curl -s "https://api.dxqso.net/logbook/download?format=json" \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION"
# Incremental sync — only QSOs changed since a date
curl -s "https://api.dxqso.net/logbook/download?format=json&qso_since=2025-01-01T00:00:00" \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION"
# Pagination — pass next token header
curl -s "https://api.dxqso.net/logbook/download?format=json" \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION" \
-H "X-Next-Token: eyJjcmVhdGVkX2F0IjoiM..."
# Save as ADIF file
curl -s "https://api.dxqso.net/logbook/download?format=adif" \
-H "Authorization: Bearer $VENDOR" \
-H "dxqso-station-token: Bearer $STATION" \
-o my_logbook.adi