afu.tools

Home › API

Open API for amateur radio data

Everything this site shows comes from a read-only API. It returns JSON and may be used in your own programs as long as afu.tools is credited as the source. Access needs a key: free, with an afu.tools account.

Overview

https://afu.tools/api/v1 returns everything this site shows: band plan with class privileges, repeaters with radius search and CSV export, DOK directory, licence exam dates with feeds, contest calendar, POTA and SOTA references, propagation data and callsign lookups.

The data is collected regularly from official and club sources and served as ready-made snapshots. Which source is refreshed how often is listed below.

For each area there is a real, shortened example response to expand, so you can see the field names before writing your first request. And every response says itself which sources must be credited when passing the data on: Attribution.

Access

Every request needs a key. It makes the rate limit count towards you and not towards your IP address, which others may share. Keys are free with an afu.tools account.

Already have a key from before, requested without an account? It stays valid. If you create an account with the same email address, it shows up there by itself. Stuck? info@afu.tools.

Sent as a header or as a bearer token:

$ curl -H "X-API-Key: afu_…" https://afu.tools/api/v1/bands
$ curl -H "Authorization: Bearer afu_…" https://afu.tools/api/v1/bands

Without a key: status 401. Exceptions are /api/v1/health and the subscription addresses ending in feed.ics or feed.rss. A calendar app or feed reader can't send a key, and a subscription that only works in a browser wouldn't be one. The server recognises the afu.tools pages and our other own applications by their origin; they don't need one either.

The files under /data/ belong to the site and answer 403 from outside. The same data is available via the endpoints below, filtered and with radius search.

We count which access calls which endpoint how often and from which address. Not the query itself: for a callsign lookup, the record says it was used, not who was searched for. More in the privacy notice.

Ground rules

  • Credit the sourceIf you use the data, credit afu.tools and the original source, visible to whoever sees the result. Which ones those are, every response tells you itself: Attribution.
  • GET onlyResponses are JSON in UTF-8. Exceptions are the CSV exports and the feeds.
  • Rate limiting120 requests per minute and key, 30 for callsign lookups, 10 for CSV exports. Above that you get status 429 with Retry-After.
  • Cross-origin allowedThe API can be called directly from a web page.
  • Error format{"error": "…", "status": 404}
  • Machine-readable/api/v1/openapi.json. There is deliberately no Swagger UI, it loads its scripts from a third-party CDN.

Attribution in every response

Anyone passing data on has to say where it comes from, and for that they need to know who. So it isn't just on a page you'd have to look up: every response carries the attribution. In JSON as the block quellenangabe, for CSV exports and feeds, which aren't JSON, as the header X-Quellen. The line from nennung is the one that must appear with the result.

{
  "nennung": "Quellen: afu.tools; Bundesnetzagentur",
  "hinweis": "Wer diese Daten weitergibt oder anzeigt, nennt die hier aufgeführten Quellen sichtbar für den, der das Ergebnis sieht: die ursprüngliche Quelle und afu.tools.",
  "doku": "https://afu.tools/api#quellenangabe",
  "zusammenstellung": {
    "name": "afu.tools",
    "url": "https://afu.tools",
    "lizenz": "Nennung von afu.tools"
  },
  "datensaetze": [
    {
      "id": "exams",
      "datensatz": "Prüfungstermine",
      "wofuer": "Termine mit Umkreissuche, Kalender- und RSS-Abo",
      "stand": "2026-09-01T01:25:00Z",
      "quellen": [
        {
          "name": "Bundesnetzagentur",
          "url": "https://www.bundesnetzagentur.de/DE/Fachthemen/Telekommunikation/Frequenzen/SpezielleAnwendungen/Amateurfunk/start.html",
          "lizenz": "amtliche Veröffentlichung"
        }
      ]
    }
  ]
}
  • nennungThe ready-made line to print, sources separated by semicolons. Source names contain commas themselves, hence no commas.
  • datensaetzeWhat the response is made of. Per dataset the identifier, what it is used for, the time of the last fetch (stand, null for anything queried live) and the sources with name, address and terms.
  • zusammenstellungafu.tools itself. The compilation is work and is credited too, even if a response happens to come from no third-party source.
  • lizenz nullNo explicit terms known. Then the ground rule applies: credit the source.
  • HeadersX-Quellen carries the same line, X-Quellen-Doku points here. Both are on every response, CSV and feeds included.
  • FeedsIn the calendar the attribution is in X-WR-CALDESC, in RSS in <copyright>.

The full version, i.e. all datasets with terms and freshness plus the mapping from endpoint to dataset, is at /api/v1/quellen. For humans, the same list is on the Sources page.

GET/api/v1/quellen
Example response GET /api/v1/quellen
{
  "quellenangabe": { … },
  "generated": "2026-09-01T08:01:15Z",
  "hinweis": "Wer diese Daten weitergibt oder anzeigt, nennt die hier aufgeführten Quellen sichtbar für den, der das Ergebnis sieht: die ursprüngliche Quelle und afu.tools.",
  "zusammenstellung": {
    "name": "afu.tools",
    "url": "https://afu.tools",
    "lizenz": "Nennung von afu.tools"
  },
  "doku": "https://afu.tools/api#quellenangabe",
  "count": 20,
  "datensaetze": [
    {
      "id": "anlaufstellen",
      "datensatz": "Anlaufstellen je Betriebsart",
      "wofuer": "Digimode- und Anruffrequenzen",
      "stand": "2026-09-01T04:07:01Z",
      "quellen": [
        {
          "name": "Vorgabefrequenzen von WSJT-X",
          "url": "https://wsjt.sourceforge.io/",
          "lizenz": null
        },
        {
          "name": "Vorgabefrequenzen von JS8Call",
          "url": "http://js8call.com/",
          "lizenz": null
        },
        {
          "name": "Bandplan der IARU Region 1",
          "url": "https://www.iaru-r1.org/reference/band-plans/",
          "lizenz": null
        },
        {
          "name": "Emergency Centres of Activity, IARU Region 1",
          "url": "https://www.iaru-r1.org/on-the-air/emergency-communications/",
          "lizenz": null
        },
        {
          "name": "NCDXF/IARU International Beacon Project",
          "url": "https://www.ncdxf.org/beacon/",
          "lizenz": null
        }
      ]
    },
    "... 19 weitere"
  ],
  "endpunkte": {
    "/": [],
    "/anlaufstellen": [
      "anlaufstellen"
    ],
    "/ausland": [
      "ausland"
    ],
    "...": "47 weitere"
  },
  "programme": [
    {
      "name": "Leaflet 1.9.4",
      "url": "https://leafletjs.com",
      "lizenz": "BSD-2-Clause"
    },
    "... 1 weitere"
  ]
}

In the examples further down, this block is shortened to "quellenangabe": { … } and long lists are cut down to one or two entries; the rest appears as "… N weitere" (N more). Otherwise every example would say the same thing three times.

Band plan

GET/api/v1/bands
GET/api/v1/bands/20m
GET/api/v1/frequency/145.500
GET/api/v1/frequency/14070kHz

Returns per band the allocations under Annex 1 of the Amateurfunkverordnung (AFuV, German amateur radio regulations) with status, permitted power per class and the additional provisions, plus the segments of the IARU band plan. /frequency answers the question of what applies at a given frequency.

Example response GET /api/v1/bands/20m
{
  "quellenangabe": { … },
  "key": "20m",
  "label": "20 m",
  "from_hz": 14000000,
  "to_hz": 14350000,
  "allocations": [
    {
      "from_hz": 14000000,
      "to_hz": 14350000,
      "status": "P",
      "classes": {
        "A": {
          "value": 750,
          "unit": "PEP"
        },
        "E": null,
        "N": null
      },
      "notes": [
        3,
        13
      ]
    }
  ],
  "segments": [
    {
      "from_hz": 14000000,
      "to_hz": 14060000,
      "max_bandwidth_hz": 200,
      "mode": "CW",
      "usage": null
    },
    {
      "from_hz": 14060000,
      "to_hz": 14350000,
      "max_bandwidth_hz": 2700,
      "mode": "All modes",
      "usage": null
    }
  ],
  "activities": [
    {
      "freq_hz": 14060000,
      "description": "CW QRP Centre of Activity"
    },
    {
      "freq_hz": 14070000,
      "description": "PSK Centre of Activity"
    },
    {
      "freq_hz": 14074000,
      "description": "FT8 Centre of Activity"
    },
    {
      "freq_hz": 14100000,
      "description": "International Beacon Project"
    },
    {
      "freq_hz": 14300000,
      "description": "Global Emergency Centre of Activity"
    }
  ]
}
Example response GET /api/v1/frequency/145.500
{
  "quellenangabe": { … },
  "freq_hz": 145500000.0,
  "band": "2m",
  "label": "2 m",
  "allocation": {
    "from_hz": 144000000,
    "to_hz": 146000000,
    "status": "P",
    "classes": {
      "A": {
        "value": 750,
        "unit": "PEP"
      },
      "E": {
        "value": 75,
        "unit": "PEP"
      },
      "N": {
        "value": 6.1,
        "unit": "ERP"
      }
    },
    "notes": [
      6,
      13
    ]
  },
  "segments": [
    {
      "from_hz": 144100000,
      "to_hz": 146000000,
      "max_bandwidth_hz": 2700,
      "mode": "All modes",
      "usage": null
    }
  ],
  "notes": {
    "6": "Belegte Bandbreite höchstens 40 kHz.",
    "13": "Teile des Bereichs sind auch dem Amateurfunkdienst über Satelliten zugewiesen."
  },
  "source": {
    "name": "Anlage 1 AFuV (Nutzungsbedingungen Amateurfunkdienst)",
    "url": "https://www.gesetze-im-internet.de/afuv_2005/anlage_1.html",
    "retrieved": "2026-08-23"
  }
}

Repeaters

GET/api/v1/repeaters?band=2m&mode=FM&near=JO50HB&km=50
GET/api/v1/repeaters?q=DB0
GET/api/v1/repeaters?source=fm-funknetz.de
GET/api/v1/repeaters?mode=FM-Funknetz&near=JO50HB&km=100
GET/api/v1/repeaters/export.csv?format=chirp&near=JO50HB&km=50
GET/api/v1/repeaters/export.csv?format=rigstudio&band=70cm

Filter by band, mode, source and with q across callsign, place, locator, network and remarks. If a station appears in several sources, it is one entry with a list under sources. near takes a locator or lat,lon; with a radius, distance and bearing come back too. The CSV exports use the format of common radio programming software, or they go straight into Rigstudio.

FieldMeaning
tone_hzCTCSS frequency in hertz
tone_mode tone transmit only, tsql also decode, dcs, 1750
dcsDCS code, three digits
call_1750Repeater opens with the 1750 Hz tone burst
tone_raworiginal text from the source, for unclear entries

Tone details are maintained by the repeater keepers themselves and are patchy accordingly.

Example response GET /api/v1/repeaters?band=2m&near=JO50HB&km=25&limit=2
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:00Z",
  "sources": [
    "fm-funknetz.de",
    "repeatermap.de"
  ],
  "tone_note": "Tonangaben stammen aus den Freitextfeldern der Quellen und sind von den Betreibern gepflegt. Nicht jeder Eintrag ist aktuell.",
  "count": 2,
  "repeaters": [
    {
      "callsign": "DB0NH",
      "rx_mhz": 145.3375,
      "tx_mhz": 145.3375,
      "offset_mhz": 0.0,
      "tone_hz": 88.5,
      "tone_mode": "tone",
      "dcs": null,
      "call_1750": false,
      "tone_raw": "88.5 Hz",
      "mode": "FM",
      "netz": "FM-Funknetz",
      "band": "2m",
      "qth": "Bühl, Ufr., BY",
      "locator": "JO50HB",
      "keeper": "DL1XXX",
      "talkgroups": "96126",
      "remarks": null,
      "lat": 50.075,
      "lon": 10.6663,
      "source": "fm-funknetz.de",
      "last_seen": "2026-09-01",
      "sources": [
        "fm-funknetz.de"
      ],
      "distance_km": 3.3,
      "bearing_deg": 65
    },
    "... 1 weitere"
  ]
}
Example response GET /api/v1/repeaters/export.csv?format=chirp&band=2m&near=JO50HB&km=25
Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,Mode,TStep,Skip,Comment
1,DB0NH,145.337500,,0.000000,Tone,88.5,88.5,023,NN,FM,5.00,,"Bühl, Ufr., BY FM-Funknetz JO50HB"
2,DB0GH,144.850000,,0.000000,Tone,88.5,88.5,023,NN,FM,5.00,,"Gaedheim, BY FM-Funknetz JO50EA"

DOK and local clubs

GET/api/v1/dok/B37
GET/api/v1/dok/B
GET/api/v1/dok?q=Hassberge
GET/api/v1/sdok?q=Aktivit
Example response GET /api/v1/dok/B37
{
  "quellenangabe": { … },
  "type": "ov",
  "ov": {
    "id": "B37",
    "name": "Hassberge",
    "link": "https://www.darc.de/der-club/distrikte/b/ortsverbaende/37/",
    "chairman_call": "DK5DEN",
    "district_id": "B"
  },
  "district": {
    "id": "B",
    "name": "Franken",
    "link": "https://www.darc.de/der-club/distrikte/b/",
    "chairman_call": "DG4NBI"
  }
}
Example response GET /api/v1/sdok?q=Aktivit&limit=2
{
  "quellenangabe": { … },
  "generated": "2026-09-01T01:25:00Z",
  "count": 417,
  "sdok": [
    {
      "sdok": "DLFF26",
      "reason": "WWFF Aktivitäten",
      "callsign": "DP44WCA",
      "start": "2026-04-28",
      "end": "2026-12-31",
      "dok": "U23"
    },
    {
      "sdok": "SOP26",
      "reason": "Aktivitätsmonat für Diplom Sea Of Peace",
      "callsign": "DL0SOP",
      "start": "2026-07-01",
      "end": "2026-07-31",
      "dok": "V04"
    }
  ]
}

Exam dates

GET/api/v1/exams?status=verfuegbar
GET/api/v1/exams?near=JO50HB&km=200&klasse=Klasse%20N
GET/api/v1/exams/history
GET/api/v1/exams/feed.rss?near=JO50HB&km=200
GET/api/v1/exams/feed.ics?status=alle

The feeds take the same filters as the list. status=alle includes fully booked dates too, otherwise only free places are returned.

Example response GET /api/v1/exams?near=JO50HB&km=200
{
  "quellenangabe": { … },
  "generated": "2026-09-01T01:25:00Z",
  "source": {
    "name": "Bundesnetzagentur, Prüfungstermine Amateurfunk",
    "url": "https://www.bundesnetzagentur.de/DE/Fachthemen/Telekommunikation/Frequenzen/SpezielleAnwendungen/Amateurfunk/start.html"
  },
  "count": 15,
  "exams": [
    {
      "id": 20,
      "date": "2026-09-08",
      "ort": "Eschborn",
      "standort": "Eschborn",
      "tageszeit": null,
      "pruefung": "Alle Prüfungen",
      "code": null,
      "status": "ausgebucht",
      "first_seen": "2026-06-21 20:57:47",
      "available_since": null,
      "booked_since": "2026-06-21 20:57:47",
      "stand": "28. August 2026",
      "lat": 50.1433,
      "lon": 8.5706,
      "distance_km": 147
    },
    {
      "id": 84,
      "date": "2026-09-08",
      "ort": "Nürnberg",
      "standort": "Nürnberg",
      "tageszeit": null,
      "pruefung": "Alle Prüfungen",
      "code": null,
      "status": "ausgebucht",
      "first_seen": "2026-08-19 06:00:00",
      "available_since": null,
      "booked_since": "2026-08-19 06:00:00",
      "stand": "28. August 2026",
      "lat": 49.4521,
      "lon": 11.0767,
      "distance_km": 75
    },
    "... 13 weitere"
  ]
}

Contest calendar

GET/api/v1/contests?days=365&klasse=N
GET/api/v1/contests/feed.ics?klasse=N

The dates are calculated from the published contest rules; each entry names the rule and links the rules. bands are the bands in the rules, klassen says per German licence class (N, E, A) which of them it may use according to the band plan. A class without a band is missing there. With klasse=N (or E, A), list and calendar subscription keep only the contests where this class has at least one band. Dates only listed in the DARC contest calendar carry "quelle": "darc"; the regular activity evenings among them ("art": "aktivitaet", such as CWT, NRAU or RSGB activity contests) only come with aktivitaeten=1, in the list as in the subscription.

Example response GET /api/v1/contests?days=30
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:01Z",
  "note": "Die Termine sind aus den Wiederholungsregeln der Ausschreibungen berechnet und gegen die Termine des Jahres 2026 gegengeprüft. Einzelne Ausgaben werden gelegentlich verschoben, verbindlich ist die Ausschreibung des Veranstalters. Wo keine Uhrzeit steht, war sie nicht gesichert, sie steht dann in der Ausschreibung.",
  "count": 7,
  "contests": [
    {
      "key": "aa-ssb",
      "name": "All Asian DX Contest, SSB",
      "mode": "SSB",
      "rule": "erstes volles September-Wochenende",
      "url": "https://www.jarl.org/English/4_Library/A-4-3_Contests/2023AADX_rule.htm",
      "date": "2026-09-05",
      "start": "2026-09-05T00:00:00Z",
      "end": "2026-09-07T00:00:00Z",
      "allday": false
    },
    {
      "key": "iaru-fd-ssb",
      "name": "IARU Region 1 Fieldday, SSB",
      "mode": "SSB",
      "rule": "erstes volles September-Wochenende",
      "url": "https://www.darc.de/der-club/referate/conteste/",
      "date": "2026-09-05",
      "start": "2026-09-05T13:00:00Z",
      "end": "2026-09-06T12:59:00Z",
      "allday": false
    },
    "... 5 weitere"
  ]
}

DX calendar

GET/api/v1/dx?days=90&art=aktivitaet
GET/api/v1/dx?cont=OC,AS&dxcc=3B7,VP8
GET/api/v1/dx?iota=OC-008&belegt=2
GET/api/v1/dx/feed.ics?cont=OC&alarm=3
GET/api/v1/dx/feed.rss?dxcc=3Y

Announced DX activities, merged from three public calendars. Only the date is taken over; country, continent, CQ and ITU zone are derived from the callsign. The description of the activity stays with the source, every entry links there.

Filters: cont (continent, two letters), dxcc (prefix of the entity), iota, call, entity, cq, itu, art (aktivitaet up to 90 days or dauer), days, q, belegt (requires a minimum number of sources naming the date), beleg (see below), stufe (rarity of the entity: sehr_selten, selten, ungewoehnlich, haeufig) and typ (dxpedition, sonder for special callsigns or sonstige). Several values per filter separated by commas. alarm sets a reminder in the calendar subscription that many days before the start.

Calendar and feed addresses need no access key: a calendar app can't send one. Everything else follows the ground rules above.

Analyses across the archive

GET/api/v1/dx/bestand
GET/api/v1/dx/statistik?sortierung=selten&bis=2025
GET/api/v1/dx/verlauf?cont=OC&von=2015
GET/api/v1/dx/entitaet/3B6

Announced activities are recorded, and the NG3K yearly archive back to 1996 has been imported. That answers what the current list alone can't: how often an entity was announced at all, when last and at what intervals.

statistik groups per DXCC entity and gives count, years, first and last announcement, total and average duration and luecke_tage, the mean gap between two announcements. Sort with sortierung (anzahl, selten, zuletzt, laenge, name), filter as above with cont, dxcc, iota, art plus von and bis as years. verlauf returns the same selection per year, entitaet all individual dates of one entity.

Did it really happen?

No announcement source knows that. So every activity gets an evidence level in the field beleg, built from three free sources: the user list of Club Log (first and last QSO per log), the activity list of LoTW and the daily archives of the Reverse Beacon Network. You can filter by it wherever the other filters work, feeds and calendars included.

ValueMeaning
logLog uploaded to Club Log, the QSO period matches the announcement. Strongest evidence.
gehoertVerifiably heard during the announced period. Accurate to the day, but only covers CW and RTTY as long as the evidence comes from the RBN archive.
ruf_bekanntThe callsign is known to Club Log or LoTW, but there is no log for this period. That is no evidence for this activity, just the information that the callsign has been on the air before. Typical for permanent stations whose combined log runs for years.
keine_spurNothing found. That is no proof to the contrary: anyone who only QSLs direct and uploads nowhere leaves none of these traces.
offenNot yet assessable: in the future, still running, or ended less than 60 days ago. Something that hasn't happened yet can't have a log. Positive evidence still counts immediately, so a running activity can already be gehoert (heard).
unpruefbarThe announcement doesn't name a real callsign, just a placeholder. Without a callsign there's nothing to look up.

What the numbers are and what they aren't: they count announcements, not confirmed activities. None of the sources records whether an expedition really took place. Also listed is what is interesting for DXers: Estonia appearing only once doesn't mean nobody operates there, but that it was rarely the destination of an announced activity. The year 2022 is missing at the source; /dx/bestand names such gaps explicitly.

Example response GET /api/v1/dx?cont=OC&limit=2
{
  "quellenangabe": { … },
  "generated": "2026-09-01T03:40:05Z",
  "note": "Angekündigt ist nicht zugesagt: Termine verschieben sich, maßgeblich ist die Ankündigung der Expedition selbst.",
  "quellen": [
    {
      "name": "ADXO (NG3K, Bill Feidt)",
      "url": "https://www.ng3k.com/Misc/adxo.html",
      "abruf": "https://www.ng3k.com/Misc/adxo.html",
      "key": "ng3k"
    },
    {
      "name": "DX Calendar (VA3RJ)",
      "url": "https://www.qsl.net/va3rj/dx_cal.html",
      "abruf": "https://www.qsl.net/va3rj/dx_cal.html",
      "key": "va3rj"
    },
    "... 1 weitere"
  ],
  "dxcc_quelle": {
    "name": "BIG CTY (AD1C, Jim Reisert)",
    "url": "https://www.country-files.com/big-cty/",
    "lizenz": "MIT"
  },
  "kontinente": {
    "AF": "Afrika",
    "AN": "Antarktis",
    "...": "5 weitere"
  },
  "count": 31,
  "dxpeditions": [
    {
      "call": "V73HW",
      "start": "2025-03-15",
      "end": "2027-03-15",
      "tage": 731,
      "iota": "OC-028",
      "qsl": null,
      "unsicher": true,
      "quellen": [
        {
          "key": "va3rj",
          "name": "DX Calendar (VA3RJ)",
          "url": "https://www.qsl.net/va3rj/dx_cal.html"
        }
      ],
      "art": "dauer",
      "entity": "Marshall Islands",
      "dxcc": "V7",
      "cont": "OC",
      "cq": 31,
      "itu": 65,
      "lat": 9.08,
      "lon": 167.33,
      "beleg": "offen",
      "beleg_detail": "läuft noch",
      "stufe": "ungewoehnlich",
      "stufe_grund": "GDXF Most Wanted Platz 53, 62.7 % der Teilnehmer fehlt das Gebiet noch",
      "aktive_rufzeichen": 54,
      "tage_pro_jahr": 17.28,
      "gehoert_tage": 0,
      "beobachtet_tage": 2,
      "gdxf_rang": 53,
      "gdxf_prozent": 62.7,
      "zuletzt_angekuendigt": "2026-05-23",
      "typ": "dxpedition"
    },
    "... 1 weitere"
  ]
}
Example response GET /api/v1/dx/statistik?sortierung=selten&limit=2
{
  "quellenangabe": { … },
  "hinweis": "Gezählt sind Ankündigungen, keine bestätigten Aktivitäten. Die Quellen führen auf, was für DX-Betrieb angekündigt wurde; ein Land, das selten auftaucht, ist deshalb selten Reiseziel und nicht unbedingt selten in der Luft.",
  "gesamt": {
    "n": 12860,
    "entitaeten": 343,
    "von": 1996,
    "bis": 2027,
    "belegt": 3663
  },
  "count": 2,
  "entitaeten": [
    {
      "dxcc": "ES",
      "entitaet": "Estonia",
      "kontinent": "EU",
      "n": 1,
      "jahre": 1,
      "erst": "1997-06-26",
      "letzt": "1997-06-26",
      "tage_gesamt": 5,
      "tage_schnitt": 5.0,
      "belegt": 0,
      "luecke_tage": null
    },
    {
      "dxcc": "CX",
      "entitaet": "Uruguay",
      "kontinent": "SA",
      "n": 1,
      "jahre": 1,
      "erst": "1999-11-14",
      "letzt": "1999-11-14",
      "tage_gesamt": 10,
      "tage_schnitt": 10.0,
      "belegt": 0,
      "luecke_tage": null
    }
  ]
}
Example response GET /api/v1/dx/bestand
{
  "quellenangabe": { … },
  "hinweis": "Gezählt sind Ankündigungen, keine bestätigten Aktivitäten. Die Quellen führen auf, was für DX-Betrieb angekündigt wurde; ein Land, das selten auftaucht, ist deshalb selten Reiseziel und nicht unbedingt selten in der Luft.",
  "aktivitaeten": 12883,
  "von": "1996-06-16",
  "bis": "2028-01-01",
  "entitaeten": 343,
  "jahre": 31,
  "fehlende_jahrgaenge": [
    2022
  ],
  "quellen": [
    {
      "quellen": "ng3k-archiv",
      "n": 12555
    },
    {
      "quellen": "va3rj",
      "n": 223
    },
    "... 6 weitere"
  ],
  "belege": {
    "unbewertet": 2,
    "gehoert": 1856,
    "keine_spur": 5172,
    "log": 1807,
    "offen": 272,
    "ruf_bekannt": 1769,
    "unpruefbar": 2005
  },
  "hoerbelege": [
    {
      "quelle": "cluster",
      "tage": 43,
      "rufzeichen": 36
    },
    {
      "quelle": "dxcache",
      "tage": 43,
      "rufzeichen": 36
    },
    "... 1 weitere"
  ],
  "beleg_stufen": {
    "log": "Log bei Club Log hochgeladen, QSO-Zeitraum überlappt die Ankündigung",
    "gehoert": "im angekündigten Zeitraum nachweislich gehört (RBN oder Cluster)",
    "...": "4 weitere"
  }
}

/dx/spots also filters with quelle (cluster, dxcache, rbn, combinable with commas). An RBN spot also carries snr_db (signal-to-noise ratio) and wpm (speed, for RTTY/PSK the BPS equivalent); for a cluster spot both are null. View with filters, distance and bearing at /rbn, as on the DX cluster.

Example response GET /api/v1/dx/spots?minuten=240&limit=2
{
  "quellenangabe": { … },
  "count": 2,
  "fenster_minuten": 240,
  "bestand": {
    "n": 14944,
    "von": "2026-08-31T07:41:33Z",
    "bis": "2026-09-01T08:02:12Z"
  },
  "stufen": {
    "sehr_selten": "sehr selten, kaum eine Funkstelle vor Ort",
    "selten": "selten, nur wenige Funkstellen vor Ort",
    "ungewoehnlich": "ungewöhnlich, überschaubar viele Funkstellen",
    "haeufig": "häufig"
  },
  "hinweis_modus": "`modus` steht so im Spot. `modus_vermutet` ist aus der Frequenz abgeleitet und nur eine Schätzung.",
  "spots": [
    {
      "zeit": "2026-09-01T08:02:12Z",
      "call": "HB9XXX",
      "freq_khz": 7075.0,
      "band": "40m",
      "modus": null,
      "spotter": "DL1XXX",
      "bemerkung": "POTA CH-0219 tnx p2p DE-0531",
      "quelle": "cluster",
      "dxcc": "HB",
      "entitaet": "Switzerland",
      "kontinent": "EU",
      "cq": 14,
      "itu": 28,
      "lat": 46.87,
      "lon": 8.12,
      "snr_db": null,
      "wpm": null,
      "stufe": "haeufig",
      "stufe_grund": "nicht in den GDXF-Top-150; Betrieb vor Ort; Mitschnitt läuft erst 2 Tage und zählt noch nicht mit",
      "gdxf_rang": null,
      "gdxf_prozent": null,
      "aktive_rufzeichen": 1741,
      "tage_pro_jahr": 0.55,
      "gehoert_tage": 1,
      "beobachtet_tage": 2,
      "zuletzt_angekuendigt": "2001-04-24",
      "ank_start": null,
      "ank_ende": null,
      "ank_iota": null,
      "beleg": null,
      "modus_vermutet": "FT8"
    },
    "... 1 weitere"
  ]
}

Who hears me: Reverse Beacon Network and PSK Reporter

GET/api/v1/dx/gehoert/DL1ABC?minuten=1440
GET/api/v1/dx/gehoert/DL1ABC?quelle=psk&modus=FT8&band=20m
GET/api/v1/dx/rbn/DL1ABC
GET/api/v1/dx/rbn/DL1ABC?minuten=1440&band=20m,40m&modus=CW

All RBN reports for a callsign in the period (minuten, 5 to 2880, default 60), also with a suffix such as /P. skimmer summarises per skimmer: number of reports, best and average signal, bands, first and last report, plus the location from the RBN skimmer list (locator, lat, lon). If a skimmer isn't in the list, genau is false and the location is the centre of its country. je_band counts without the band filter, spots are the latest individual reports (limit, at most 1000). Reports are kept for two days. View with map at /rbn.

/dx/gehoert combines three sources (quelle, comma-separated, default all): rbn (RBN, CW and RTTY), rbndigi (RBN, FT8 and FT4) and psk (PSK Reporter). Instead of skimmer the list is called empfaenger, per receiver with quellen, modi and location (for PSK Reporter from the receiver's locator). psk says where the PSK part comes from: mitschnitt for callsigns from DE, AT, CH, LI and LU (our own capture, 48 hours), abfrage for all others (queried from PSK Reporter, 24 hours, up to five minutes old). The dB values of CW and FT8 are not comparable, they refer to different bandwidths. wolke summarises per receiver, band and mode, as a list [Empfänger, Band, Betriebsart, bestes dB, mittleres dB, Meldungen] (receiver, band, mode, best dB, average dB, reports), at most 5000 rows; /rbn uses it to plot signal against distance. With stumm=1 you also get nicht_gehoert: the receivers that reported others on the same bands and modes in the period but not this callsign, aggregated to 0.2 degrees as [lat, lon, Zahl, genau, digital] (digital 0: CW and RTTY). Counted to the hour; for PSK Reporter our own capture only knows receivers that hear stations from DACH.

Operating abroad

GET/api/v1/ausland
GET/api/v1/ausland?klasse=e&moeglich=true
GET/api/v1/ausland?land=spanien
GET/api/v1/ausland?region=2
GET/api/v1/ausland?rufzeichen=HB9XYZ

rufzeichen resolves a heard callsign to exactly one country via the ITU callsign series and returns the series too. A fixed pair beats a single character, otherwise K3… would land at every K country instead of the USA. If no series is found, you get 404.

All countries of the world with the question of whether a German class A, E or N licence allows operating there. With moeglich=true a guest licence counts as possible, it just has to be applied for in advance. Each entry also lists the ITU region, the ITU callsign series, the time zones and the centre of the country, plus the addresses of the national society and the responsible administration. Where each of these details comes from is in quellen and sachquellen of the response.

Example response GET /api/v1/ausland?rufzeichen=HB9XYZ
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:01Z",
  "rufzeichen": "HB9XYZ",
  "serie": "HB",
  "quellen": {
    "cept": {
      "name": "CEPT-Empfehlung T/R 61-01",
      "ausgabe": "Ausgabe 18. Oktober 2024",
      "url": "https://docdb.cept.org/Docs/doc98/official/pdf/TR6101.PDF"
    },
    "umsetzung_a": {
      "name": "ECO, Umsetzungsstand T/R 61-01",
      "ausgabe": "abgerufen am 24.08.2026",
      "url": "https://docdb.cept.org/implementation/925"
    },
    "...": "3 weitere"
  },
  "sachquellen": [
    {
      "feld": "Mittelpunkt, Entfernung, Peilung",
      "name": "Google Public Data, countries.csv (dspl)",
      "url": "https://github.com/google/dspl/blob/master/samples/google/canonical/countries.csv"
    },
    {
      "feld": "Rufzeichenserie",
      "name": "ITU, Anhang 42 der Vollzugsordnung für den Funkdienst, über die Wikipedia-Aufstellung „ITU prefix“",
      "url": "https://en.wikipedia.org/wiki/ITU_prefix"
    },
    "... 2 weitere"
  ],
  "count": 1,
  "laender": [
    {
      "code": "CH",
      "land": "Schweiz",
      "a": "cept",
      "e": "ja",
      "n": "nein",
      "praefix_cept": "HB9",
      "hinweis": "Klasse A laut CEPT-Datenbank umgesetzt, Stand 01.09.2009. Klasse E laut CEPT-Datenbank umgesetzt, Stand 01.09.2009.",
      "links": [
        {
          "titel": "Union Schweizerischer Kurzwellen-Amateure [USKA]",
          "url": "http://www.uska.ch"
        }
      ],
      "koord": [
        46.8182,
        8.2275
      ],
      "itu_region": 1,
      "praefixe": [
        "HB",
        "HE"
      ],
      "zonen": [
        "Europe/Zurich"
      ]
    }
  ]
}

Propagation

GET/api/v1/funkwetter
GET/api/v1/funkwetter?kurz=1
GET/api/v1/funkwetter/offen?von=dl
GET/api/v1/funkwetter/sonde/JR055

What the /funkwetter page shows, recalculated every 15 minutes: kopf with SFI, sunspots, A, Kp, X-ray class, solar wind, Bz, MUF and foEs over DL; lage as one sentence; baender per HF band with tag, nacht and jetzt (good, fair, poor, closed); vhf with sporadic E per band and aurora; karte with the MUF grid (4°, MHz × 10, centre of the first cell in lat0/lon0), the measurement density per cell (0 to 9) and the contour lines as GeoJSON; sonden with the last reliable value of each ionosonde and its operator; plus the series of Kp, X-ray, solar wind, D-RAP and aurora. verlauf is the hourly state for the last tage days (default 30). With kurz=1 only header, summary and bands.

/funkwetter/offen counts how many RBN and PSK Reporter receivers have heard stations from DL (von=dl) or Europe (von=eu) in the last 30 minutes, per band and continent. /funkwetter/sonde/{URSI} returns the values of one ionosonde over 48 hours as [t, CS, foF2, foEs, MUF(3000)].

If you pass on the ionosonde values, credit GIRO (Reinisch and Galkin 2011) and the operator of the ionosonde; the values are under CC BY-NC-SA 4.0, so not for commercial use. The embeddable images (/funkwetter/bild/baender.png, muf.png, banner.png, also as .svg, with ?thema=dunkel) need no access key.

Example response GET /api/v1/funkwetter/offen?von=dl (shortened)
{
  "quellenangabe": { … },
  "baender": ["160m", "80m", "…", "2m"],
  "kontinente": ["EU", "NA", "SA", "AS", "AF", "OC"],
  "matrix": {
    "20m": {
      "NA": { "empf": 41, "meldungen": 512, "snr": 31 },
      "AS": { "empf": 12, "meldungen": 64, "snr": 22 }
    }
  },
  "sender": 119,
  "minuten": 30,
  "von": "dl",
  "stand": "2026-10-07T11:00:00Z"
}

Portable

GET/api/v1/spots?near=JO50HB&km=25
GET/api/v1/spots?near=50.07,10.67&km=50&type=sota
GET/api/v1/solar
Example response GET /api/v1/spots?near=JO50HB&km=25&limit=2
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:01Z",
  "count": 10,
  "spots": [
    {
      "type": "pota",
      "ref": "DE-0581",
      "name": "Hohe Wann Nature Reserve",
      "lat": 50.0366,
      "lon": 10.5747,
      "distance_km": 4.6,
      "bearing_deg": 231
    },
    "... 1 weitere"
  ]
}
Example response GET /api/v1/solar
{
  "quellenangabe": { … },
  "generated": "2026-10-07T11:00:00Z",
  "source": {
    "name": "afu.tools Funkwetter (NOAA SWPC, GFZ Potsdam, GIRO)",
    "url": "https://afu.tools/funkwetter"
  },
  "data": {
    "source": "afu.tools",
    "updated": "07 Oct 2026 1100 GMT",
    "solarflux": "113",
    "aindex": "6",
    "kindex": "0",
    "kindexnt": "",
    "xray": "B8.6",
    "sunspots": "77",
    "aurora": "30",
    "solarwind": "357.0",
    "magneticfield": "0.6",
    "geomagfield": "sehr ruhig",
    "signalnoise": "S0-S1",
    "fof2": "7.075",
    "muffactor": "",
    "muf": "25.4"
  },
  "hf": [
    {
      "band": "80m-40m",
      "time": "day",
      "condition": "Fair"
    },
    {
      "band": "30m-20m",
      "time": "day",
      "condition": "Good"
    },
    {
      "band": "17m-15m",
      "time": "day",
      "condition": "Fair"
    },
    {
      "band": "12m-10m",
      "time": "day",
      "condition": "Poor"
    },
    {
      "band": "80m-40m",
      "time": "night",
      "condition": "Good"
    },
    {
      "band": "30m-20m",
      "time": "night",
      "condition": "Poor"
    },
    {
      "band": "17m-15m",
      "time": "night",
      "condition": "Poor"
    },
    {
      "band": "12m-10m",
      "time": "night",
      "condition": "Poor"
    }
  ],
  "vhf": [
    {
      "name": "vhf-aurora",
      "location": "northern_hemi",
      "value": "High LAT AUR"
    },
    {
      "name": "E-Skip",
      "location": "europe",
      "value": "Band Closed"
    },
    {
      "name": "E-Skip",
      "location": "north_america",
      "value": "Band Closed"
    },
    {
      "name": "E-Skip",
      "location": "europe_6m",
      "value": "Band Closed"
    },
    {
      "name": "E-Skip",
      "location": "europe_4m",
      "value": "Band Closed"
    }
  ]
}

Calling frequencies

GET/api/v1/anlaufstellen
GET/api/v1/anlaufstellen?band=20m
GET/api/v1/anlaufstellen?art=digital

The established meeting points per mode: FT8, FT4, JS8, WSPR, PSK31, RTTY, SSTV, the calling frequencies, APRS, DAPNET, the emergency communication frequencies and the IBP beacons. Given is the dial frequency in upper sideband. With band you get the view of one band, sorted by frequency. Only what is also inside the band in Germany is included.

Example response GET /api/v1/anlaufstellen?band=20m
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:01Z",
  "band": "20m",
  "hinweis": "Einstellfrequenz (Dial) in oberem Seitenband. Eingebürgerte Treffpunkte, keine Vorschrift, verbindlich ist der Bandplan.",
  "quellen": [
    {
      "feld": "FT8, FT4, WSPR",
      "name": "Vorgabefrequenzen von WSJT-X",
      "url": "https://wsjt.sourceforge.io/"
    },
    {
      "feld": "JS8",
      "name": "Vorgabefrequenzen von JS8Call",
      "url": "http://js8call.com/"
    },
    "... 3 weitere"
  ],
  "anlaufstellen": [
    {
      "key": "cw",
      "name": "CW-QRP",
      "art": "telegrafie",
      "wert": 14.06
    },
    {
      "key": "psk",
      "name": "PSK31",
      "art": "digital",
      "wert": 14.07
    },
    {
      "key": "ft8",
      "name": "FT8",
      "art": "digital",
      "wert": 14.074
    },
    "... 7 weitere"
  ]
}

Prefix and DXCC

GET/api/v1/dxcc/VK6
GET/api/v1/dxcc/9M6%2FLA6VM
GET/api/v1/dxcc?cont=OC
GET/api/v1/dxcc?q=island

Entity, continent, CQ and ITU zone and the centre point for a callsign or prefix. rule says how it was mapped: ausnahme for an entry in the exception list, praefix for the prefix rule. It is based on the BIG CTY country file (MIT licence), the same one the DX calendar and cluster use.

Example response GET /api/v1/dxcc/VK6
{
  "quellenangabe": { … },
  "query": "VK6",
  "match": "VK6",
  "rule": "praefix",
  "entity": {
    "name": "Australia",
    "praefix": "VK",
    "kontinent": "OC",
    "cq": 30,
    "itu": 59,
    "lat": -23.7,
    "lon": 132.33
  },
  "continent": "Ozeanien",
  "prefixes": [
    "AX",
    "AX4",
    "AX6",
    "AX8",
    "... 16 weitere"
  ],
  "source": {
    "name": "BIG CTY (AD1C, Jim Reisert)",
    "url": "https://www.country-files.com/big-cty/",
    "lizenz": "MIT"
  }
}

Satellites

GET/api/v1/tle
GET/api/v1/tle?funk=true

Orbital data of the amateur radio satellites as CelesTrak lists them, plus frequencies and modes for the known satellites. epoche gives the time of the orbital elements, which determines the accuracy of every prediction. The passes themselves are calculated by the satellite page in the browser; your location doesn't go over the wire for that.

Example response GET /api/v1/tle?funk=true
{
  "quellenangabe": { … },
  "generated": "2026-09-01T02:15:02Z",
  "quelle": {
    "name": "CelesTrak",
    "url": "https://celestrak.org/",
    "lizenz": "frei nutzbar, Nennung erbeten"
  },
  "status_quelle": {
    "name": "AMSAT-NA, Satellitenstatus",
    "url": "https://www.amsat.org/status/"
  },
  "hinweis": "Bahndaten altern: nach einer Woche liegt die Vorhersage schon Minuten daneben. Betriebszustand der Satelliten steht bei AMSAT.",
  "count": 8,
  "satelliten": [
    {
      "norad": 7530,
      "name": "OSCAR 7",
      "kuerzel": "AO-7",
      "epoche": "2026-08-31T13:34:27Z",
      "tle1": "1 07530U 74089B   26243.56559926 -.00000046  00000+0  60356-6 0  9993",
      "tle2": "2 07530 101.9921 257.6005 0012190 349.2633 134.3126 12.53699388369965",
      "funk": [
        {
          "art": "Mode A, linear",
          "up_von": 145.85,
          "up_bis": 145.95,
          "down_von": 29.4,
          "down_bis": 29.5
        },
        {
          "art": "Mode B, linear invertierend",
          "up_von": 432.125,
          "up_bis": 432.175,
          "down_von": 145.925,
          "down_bis": 145.975
        }
      ],
      "hinweis": "Ältester noch benutzter Amateurfunksatellit. Er läuft ohne Akku allein am Sonnenlicht und wechselt die Betriebsart selbsttätig.",
      "geostationaer": false
    },
    "... 7 weitere"
  ]
}

Line of sight

GET/api/v1/sichtlinie?von=JO50HB&nach=JO50WB
GET/api/v1/sichtlinie?von=50.07,10.67&nach=JO60&hoehe_von=12&hoehe_nach=40&mhz=439

Terrain profile between two points with a verdict: frei (clear), knapp (marginal) or verdeckt (blocked). It uses the German federal elevation model (DGM200, 200 m grid), the effective earth radius (factor 4/3) and the first Fresnel zone; anteil_fresnel gives how much of it stays clear, zusaetzliche_hoehe_m how much height is missing at the starting point. Covers Germany, at most 400 km.

Example response GET /api/v1/sichtlinie?von=JO50HB&nach=JO50WB&hoehe_von=10&hoehe_nach=30
{
  "quellenangabe": { … },
  "von": {
    "lat": 50.0625,
    "lon": 10.625,
    "locator": "JO50hb"
  },
  "nach": {
    "lat": 50.0625,
    "lon": 11.875,
    "locator": "JO50wb"
  },
  "moeglich": true,
  "urteil": "verdeckt",
  "text": "Das Gelände steht in der Sichtlinie. Ohne Umweg über ein Relais oder mehr Höhe geht da nichts.",
  "strecke_km": 89.23,
  "anteil_fresnel": -1.0,
  "engster_punkt": {
    "km": 88.529,
    "hoehe": 890.6,
    "sicht": 782.1,
    "luft": -108.5,
    "fresnel": 37.8
  },
  "hoehe_start_m": 412.2,
  "hoehe_ziel_m": 758.6,
  "antenne_start_m": 10.0,
  "antenne_ziel_m": 30.0,
  "mhz": 145.0,
  "zusaetzliche_hoehe_m": 13181.4,
  "ohne_daten_punkte": 0,
  "k_faktor": 1.3333333333333333,
  "profil": [
    {
      "km": 0.0,
      "hoehe": 412.2,
      "sicht": 422.2,
      "luft": 10.0,
      "fresnel": 0.0
    },
    {
      "km": 0.349,
      "hoehe": 395.1,
      "sicht": 421.8,
      "luft": 26.7,
      "fresnel": 26.8
    },
    "... 255 weitere"
  ],
  "quelle": {
    "name": "DGM200, Bundesamt für Kartographie und Geodäsie",
    "url": "https://gdz.bkg.bund.de/index.php/default/digitale-geodaten/digitale-gelandemodelle/digitales-gelandemodell-gitterweite-200-m-dgm200.html",
    "lizenz": "GeoNutzV, Quellenvermerk © GeoBasis-DE / BKG",
    "vermerk": "© GeoBasis-DE / BKG",
    "stand": "Stand 31.12.2019"
  }
}

Callsigns

GET/api/v1/callsign/DL1XXX
GET/api/v1/callsigns?prefix=DL1
GET/api/v1/callsign/frei?muster=DL1A??
GET/api/v1/callsign/frei/plan?suffix=MH&zweck=PZ&klasse=E
GET/api/v1/rufzeichenplan
GET/api/v1/rufzeichenplan/einordnen?call=DO0ABC
GET/api/v1/qth?rufe=DL1ABC,K1ABC

Returned are callsign, holder, class, the place named in the official list and the sources. The API doesn't give out addresses, coordinates or locators from the callsign list. There is no search by name and no full dump.

qth gives, for up to 100 callsigns, the locator the station itself reported to PSK Reporter or is listed with in the RBN skimmer list, with source (psk, rbn) and age in days; when signed in, also the one from your last QSO with it (log, wavelog). The FT8, FT4 and JS8 pages use it for distance and the “New Grid” colour.

callsign/frei answers the question before applying for a vanity callsign: which ones matching a pattern are not in the list? ? stands for a letter or digit, # for a digit only. Only German callsigns are checked (DA to DR, one digit, one to three letters): D??MH gives DA0MH to DR9MH. The beginning must be fixed, at most 1296 possibilities. Only the unlisted callsigns and a few numbers come back, who holds what is not listed. And unlisted doesn't mean available: returned callsigns stay blocked for a while, and allocation is on application.

rufzeichenplan returns the callsign plan of the Bundesnetzagentur (BNetzA, German regulator; extract from Vfg. 15/2025, valid from 1 Apr 2025) as JSON: per series (DA0 to DR9 without DE and DI) and suffix type (lang: 2 or 3 letters, ein: 1 letter, sonder: 4 to 7 characters) the purposes (PZ, KS, RL, FB, SZ, AB, KSB, KSO), the purposes being phased out, the class and whether the series is still newly issued today (neu), plus occasions, time limits, additions, invalid suffixes and under bestand the number of callsigns per series, suffix length and list from the latest callsign list. rufzeichenplan/einordnen classifies a callsign, without holder: series, purpose, class, notes, errors (not in the plan, invalid suffix, wrong order of additions such as /pT), also short-term licences (DL/PA3XYZ) and direction-finding beacon identifiers.

callsign/frei/plan checks a desired suffix in all series matching zweck (PZ, KS, SONDER, RL, SZ, KSB, KSO), klasse (A, E, N) and optionally laenge (1, 2, 3, 4-7); with auslaufend=true and exterritorial=true those series too. As with callsign/frei, only the unlisted callsigns and numbers come back; suffixes containing distress or Q groups (no. 7 of the plan) aren't checked at all.

Example response GET /api/v1/callsign/DL1XXX
{
  "quellenangabe": { … },
  "callsign": "DL1XXX",
  "scope": "public",
  "operator": {
    "callsign": "DL1XXX",
    "personalCallSign": null,
    "license": "A",
    "owner": "Vorname Nachname",
    "residence": "Musterstadt",
    "sources": [
      "bnetza-pdf"
    ],
    "lastFetched": "2026-08-27T04:00:46",
    "bnetzaListNumber": 12345
  },
  "sdok": null,
  "source": {
    "name": "Bundesnetzagentur, Amateurfunk-Rufzeichenliste",
    "note": "Anschrift, Koordinaten und Locator sind nicht oeffentlich."
  }
}
Example response GET /api/v1/callsign/frei?muster=D??MH
{
  "quellenangabe": { … },
  "muster": "D??MH",
  "geprueft": 180,
  "vergeben": 93,
  "frueher_vergeben": 14,
  "frei": [
    "DA0MH",
    "DA2MH",
    "DA3MH",
    "DA4MH",
    "... 69 weitere"
  ],
  "count": 73,
  "hinweis": "Nicht gelistet heißt nicht zu haben: Zurückgegebene Rufzeichen bleiben eine Zeit lang gesperrt, und über die Zuteilung entscheidet allein die Bundesnetzagentur.",
  "source": {
    "name": "Amateurfunk-Rufzeichenliste der Bundesnetzagentur",
    "url": "https://www.bundesnetzagentur.de/DE/Fachthemen/Telekommunikation/Frequenzen/SpezielleAnwendungen/Amateurfunk/start.html"
  }
}

Places and local clubs

GET/api/v1/ort?q=Haßfurt
GET/api/v1/ort?q=97437
GET/api/v1/ort?q=DM/BM-002
GET/api/v1/ort?q=B/DL-0001
GET/api/v1/ov?near=JO50hb

ort turns a place name or postcode into coordinates and locator, always exactly one result. If the name exists more than once, the bigger place wins, and the response says which one via label. A reference of a programme that means a point in the terrain returns that point: SOTA summits (DM/BM-002), bunkers (BOTA, B/DL-0001) and castles (WCA DL-00001 or COTA BBN-001), with programm and referenz in normal form. The server asks SOTA for summits outside the German-speaking area and neighbouring countries; bunkers and castles only as far as the xOTA map lists them. Parks (POTA, WWFF) are areas and therefore not a location. The same applies wherever a parameter takes a location (near and co.).

ov names the nearest local club to a location. near takes a locator, coordinates or a place name. That is no official jurisdiction: local clubs have no borders, a DOK is an identifier and not an area.

Example response GET /api/v1/ort?q=Haßfurt
{
  "quellenangabe": { … },
  "name": "Haßfurt",
  "land": "Bayern",
  "label": "Haßfurt (Bayern)",
  "lat": 50.0352,
  "lon": 10.5156,
  "locator": "JO50ga",
  "einwohner": 13562,
  "quelle": {
    "name": "GeoNames, Ortsverzeichnis und Postleitzahlen Deutschland",
    "url": "https://www.geonames.org/",
    "lizenz": "CC BY 4.0"
  }
}
Example response GET /api/v1/ov?near=JO50hb
{
  "quellenangabe": { … },
  "dok": "B37",
  "name": "Hassberge",
  "ort": "Hassberge",
  "plz": "96126",
  "distanz_km": 15.0,
  "distrikt": "B",
  "link": "https://www.darc.de/der-club/distrikte/b/ortsverbaende/37/",
  "hinweis": "Der nächstgelegene Ortsverband, keine amtliche Zuständigkeit. Ortsverbände haben keine Grenzen.",
  "quelle": {
    "name": "DARC, Ortsverbandssuche auf darc.de",
    "url": "https://www.darc.de/home/",
    "hinweis": "Postleitzahl je Ortsverband aus der Suche, Koordinaten daraus über GeoNames"
  }
}

Calculations and status

GET/api/v1/locator?lat=50.07&lon=10.67
GET/api/v1/locator?locator=JO50HB
GET/api/v1/distance?from=JO50HB&to=JN47
GET/api/v1/health
GET/api/v1/stats
Example response GET /api/v1/locator?lat=50.07&lon=10.67
{
  "quellenangabe": { … },
  "lat": 50.07,
  "lon": 10.67,
  "locator": "JO50ib",
  "locator4": "JO50"
}
Example response GET /api/v1/distance?from=JO50HB&to=JN47
{
  "quellenangabe": { … },
  "from": {
    "lat": 50.0625,
    "lon": 10.625,
    "locator": "JO50hb"
  },
  "to": {
    "lat": 47.5,
    "lon": 9.0,
    "locator": "JN47mm"
  },
  "distance_km": 308.8,
  "bearing_deg": 203.3,
  "bearing_back_deg": 22.1
}
Example response GET /api/v1/health
{
  "quellenangabe": { … },
  "ok": true,
  "snapshots": "2026-09-01T08:01:15Z",
  "last_run": [
    "quellen"
  ],
  "tasks": {
    "bands": "2026-09-01T04:07:01Z",
    "repeaters": "2026-09-01T04:07:01Z",
    "stats": "2026-09-01T04:07:01Z",
    "...": "18 weitere"
  }
}
Example response GET /api/v1/stats
{
  "quellenangabe": { … },
  "generated": "2026-09-01T04:07:01Z",
  "counts": {
    "operators": 70098,
    "ovs": 1019,
    "sdok": 6243,
    "exams_upcoming": 49,
    "exams_free": 26,
    "repeaters": 2626
  },
  "licences": {
    "A": 58684,
    "E": 10219,
    "N": 1130
  },
  "sync": [
    {
      "key": "bnetza-pdf:lastImportedAt",
      "value": "2026-08-27T04:00:53.987Z",
      "updatedAt": "2026-08-27 04:00:53.989532"
    },
    {
      "key": "bnetza-pdf:lastModified",
      "value": "Wed, 26 Aug 2026 13:56:09 GMT",
      "updatedAt": "2026-08-27 04:00:53.977718"
    },
    "... 6 weitere"
  ]
}

Example

$ curl -s 'https://afu.tools/api/v1/frequency/145.500' | jq '.band, .allocation.classes'
"2m"
{
  "A": { "value": 750, "unit": "PEP" },
  "E": { "value": 75,  "unit": "PEP" },
  "N": { "value": 6.1, "unit": "ERP" }
}

Where the data comes from

The full version, with terms of use and the time of the last fetch, is on the Sources page. Here is the short version.

AreaSourceRefreshed
Frequencies and power Annex 1 of the Amateurfunkverordnungon change
Band segmentsIARU Region 1rarely
Repeatersfm-funknetz.de, repeatermap.de every six hours
Callsign planBundesnetzagentur, Vfg. 15/2025 (official work) by hand, checked weekly for changes
Callsigns Callsign list of the Bundesnetzagentur, supplemented by QRZ.com nightly
DOK and local clubsDARCnightly
Exam datesBundesnetzagenturnightly
POTA and SOTA POTA, SOTA, areas from OSM, BfN and EEAdaily
Space weather and propagation NOAA SWPC (public domain), GFZ Potsdam (Kp, CC BY 4.0), ionosondes via GIRO (CC BY-NC-SA 4.0), rating by afu.toolsevery 15 minutes
Calling frequencies Defaults of WSJT-X and JS8Call, band plan of IARU Region 1 on change
DXCC mappingBIG CTY (AD1C), MIT licence monthly
Satellite orbital dataCelesTrak twice a day
Elevation model DGM200, © GeoBasis-DE / BKGchecked quarterly
Places, postcodes, locators GeoNames (CC BY 4.0) by hand
Map tiles © OpenStreetMap contributors (ODbL), loaded via this site's servercontinuously
Contest calendarRules published by the organisers calculated
DX announcements ADXO (NG3K), DX Calendar (VA3RJ), 425 DX News. Only the dates are taken over, the texts stay with the source. twice a day
DX archive since 1996 ADXO yearly archives (NG3K). Dates only; 2022 is missing at the source. imported once
Evidence of whether an activity took place Club Log and ARRL LoTW (public user lists), Reverse Beacon Network (daily archives since 2009, CW and RTTY only) nightly
Rarity of an entity Most Wanted of the German DX Foundation, from the DARC DXCC scoreboard (first 150 entities). Below that, our own estimate. nightly
DXCC entity, continent, zones BIG CTY (AD1C, Jim Reisert), MIT licencemonthly

The API is a by-product of this site and not a guaranteed service. If you build it into something that has to keep running, cache responses and expect outages. Questions, or a data source missing: Contact.