DxQSO Ham Logbook Sync™ API  ·  POST /upload — up to 2000 QSOs per request  ·  GET /logbook/download — JSON or ADIF  ·  Pagination via X-Next-Token  ·  Full ADIF field support  ·  LoTW · QRZ · Club Log integration  ·  AWS API Gateway · api.dxqso.net  ·  73 de DxQSO

QSO Repository Integration Demo

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.

Bearer Token Auth POST /upload GET /logbook/download ADIF / JSON Paginated
01
API Keys
Enter once — saved in your browser's localStorage
Header: Authorization: Bearer <vendor_token>
Header: dxqso-station-token: Bearer <station_token>
Saved
Authentication model: Every API request requires two Bearer tokens.
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)
02
Station Info
GET https://api.dxqso.net/logbook/station/station_info/
Fetch station details for the authenticated user
03
Upload QSO
POST https://api.dxqso.net/upload
Push one QSO record to the user's DxQSO cloud repository (JSON format)
Mandatory fields for every QSO 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)

Click to select or drag & drop
.adi / .adif files only · up to 2000 QSOs
04
Query QSOs
GET https://api.dxqso.net/logbook/download
Fetch QSOs modified since a date/time. Download as ADIF file or view inline. Paginated via X-Next-Token.
Code Reference
Copy-paste integration examples — matched to the language your logbook app is built in
C#
C# / .NET
C++
C++ (libcurl)
Swift
Swift / Obj-C
Py
Python
$_
cURL
// 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