Before satellite navigation, aircraft found their way using radio beacons on the ground. Most of them are still there. Pilots still tune them, instrument approaches still depend on them, and they're the backup when GPS goes wrong. If you build anything that draws airways, shows approach context or plots a flight on a chart, you will need to know what they are.
This post explains the main navaid types in plain terms, then shows how to query them by API. The data has two traps in it that are worth knowing about before you build on it.
The main types
VOR (VHF Omnidirectional Range). The workhorse of en-route navigation. A VOR transmits in the VHF band between 108.00 and 117.95 MHz, and an aircraft's receiver can tell which radial it's on — its magnetic bearing from the station. "Fly the 270 radial outbound" means fly due west, magnetically, away from the beacon. For decades, airways were simply lines between VORs.
DME (Distance Measuring Equipment). A VOR tells you direction; a DME tells you distance. The aircraft sends a pulse, the station replies, and the round-trip time gives the range. That range is slant distance, measured in a straight line to the aircraft, so an aircraft at 30,000 feet directly over the station still reads about 5 nautical miles. DMEs usually sit alongside a VOR or ILS, and their channel is paired with the VOR's frequency, so tuning one tunes both.
NDB (Non-Directional Beacon). The oldest type still in service. An NDB broadcasts on low and medium frequencies, mostly a few hundred kilohertz, and the aircraft's ADF needle simply points at it. It's simple and imprecise, and its signal bends around terrain and is distorted by thunderstorms. NDBs are being retired in many countries, but plenty remain, especially in remote areas.
ILS (Instrument Landing System). Not an en-route aid but an approach aid. A localizer gives lateral guidance to the runway centreline and a glideslope gives the descent path, which together let aircraft land in low cloud and poor visibility.
TACAN and VORTAC. TACAN is the military equivalent of VOR plus DME, on UHF. A VORTAC is a VOR co-located with a TACAN, and civil aircraft use the VOR part for bearing and the TACAN's distance function as their DME.
How many are there?
To get a sense of scale, we counted the open OurAirports navaid dataset, which lists 11,008 navaids in 230 countries:
| Type | Count |
|---|---|
| NDB | 6,609 |
| VOR-DME | 2,601 |
| VORTAC | 744 |
| TACAN | 442 |
| VOR | 308 |
| DME | 167 |
| NDB-DME | 137 |
NDBs outnumber everything else combined, which says more about history than current use.
That dataset also shows the first of the two traps. It lists about 1,050 VOR-family stations in the United States. The FAA's entire VOR network was about 900 stations before it started decommissioning them, and the VOR Minimum Operational Network programme is removing roughly a third, leaving about 590 by 2030. So navaid datasets lag reality. A station can be switched off and still appear in data for a long time. For anything operational, the authority's current publication and NOTAMs are the source of truth, not a database.

Why VORs aren't gone yet
If satellite navigation is better, why keep any of this? Because GPS can fail, be jammed or be spoofed, and aviation does not accept a single point of failure. The minimum operational network is sized so that an aircraft that loses GPS can still navigate by VOR and reach an airport with a VOR- or ILS-based approach. GPS interference has become common enough in some regions that this backup is no longer theoretical — it's covered in how GPS jamming shows up in ADS-B tracks.
Looking navaids up by API
The navaids endpoint takes at least one filter: an identifier, an associated airport, a type, a country or a bounding box. Unfiltered global exports are blocked, so you can't download the world in one call.
import requests
BASE = "https://skylink-api.p.rapidapi.com"
H = {"X-RapidAPI-Key": KEY, "X-RapidAPI-Host": "skylink-api.p.rapidapi.com"}
# Everything associated with an airport
r = requests.get(f"{BASE}/navaids", headers=H, params={"airport": "KJFK"})
# A type within a country
r = requests.get(f"{BASE}/navaids", headers=H, params={"country": "US", "type": "VORTAC", "limit": 500})
# Everything in a map viewport (SW corner first, then NE)
r = requests.get(f"{BASE}/navaids", headers=H, params={"bbox": "40,-74,41,-73"})A record looks like this:
{
"ident": "JFK",
"name": "Kennedy",
"type": "VOR-DME",
"frequency_khz": 115900.0,
"latitude_deg": 40.6329,
"longitude_deg": -73.7714,
"dme_channel": "106X",
"slaved_variation_deg": -12.001,
"magnetic_variation_deg": -13.182,
"power": "HIGH",
"associated_airport": "KJFK"
}The limit defaults to 100 and tops out at 500, so a whole-country query for a common type will need narrowing by bounding box. Navaids hardly ever change, so cache them for days rather than minutes.
Trap one: every frequency is in kilohertz
frequency_khz is in kHz for every type. That's natural for an NDB on 373 kHz and confusing for a VOR, where 115900 means 115.90 MHz. Display it raw and pilots will think your app is broken.
Missing frequencies can also come through as null or as -1, and the open dataset uses -1. A naive range check treats that as a real, very low frequency.
VHF_TYPES = {"VOR", "VOR-DME", "VORTAC", "DME", "TACAN", "ILS"}
def format_frequency(navaid):
"""frequency_khz is kHz for every type, so VOR 115900 means 115.90 MHz."""
f = navaid.get("frequency_khz")
if f is None or f <= 0: # missing values can arrive as null or -1
return "n/a"
if navaid["type"] in VHF_TYPES or f >= 100_000:
return f"{f / 1000:.2f} MHz"
return f"{f:.0f} kHz" # NDBs are genuinely in the kHz bandThat gives 115.90 MHz for the Kennedy VOR and 373 kHz for a typical NDB.
Trap two: two magnetic variations
Each VOR record has two variation fields, and they're usually different. In the example, slaved_variation_deg is −12.0 and magnetic_variation_deg is −13.2. Negative means west.
magnetic_variation_deg is the variation at that location today. Magnetic north drifts slowly, so it changes over the years. slaved_variation_deg is the variation the station was aligned to when it was set up or last adjusted. A VOR's radials are fixed to that alignment, not to today's magnetic north.
So if you compute which radial a position sits on, use the slaved value:
import math
def true_bearing(lat1, lon1, lat2, lon2):
"""Initial great-circle bearing from point 1 to point 2, degrees true."""
p1, p2 = math.radians(lat1), math.radians(lat2)
dl = math.radians(lon2 - lon1)
x = math.sin(dl) * math.cos(p2)
y = math.cos(p1) * math.sin(p2) - math.sin(p1) * math.cos(p2) * math.cos(dl)
return (math.degrees(math.atan2(x, y)) + 360) % 360
def radial_from(vor, lat, lon):
"""The VOR radial a position sits on. Radials are referenced to the
variation the station was aligned to (slaved), not today's variation."""
brg = true_bearing(vor["latitude_deg"], vor["longitude_deg"], lat, lon)
variation = vor.get("slaved_variation_deg")
if variation is None:
variation = vor["magnetic_variation_deg"]
return round((brg - variation) % 360) # west variation is negative hereA point due north of the Kennedy VOR is on its 012 radial. Use today's variation instead and you get 013, which is wrong by one degree. One degree sounds small, but it puts you about a nautical mile off at 60 miles out.
Across the open dataset, the two variations differ by a median of 1.4°, and by more than 2° at about a third of VOR stations. Stations aren't realigned every time the field drifts.

What to build with it
Chart and map overlays. Draw VORs, NDBs and their identifiers on a moving map, filtered by viewport with bbox. Show the frequency formatted properly.
Approach context. Use airport to list the ILS and navaids serving a field, and add them to an airport panel next to runway data.
Flight-planning helpers. Describe a position in pilot terms — "on the 344 radial of JFK at 9 miles" — by combining radial_from with a distance calculation.
Outage awareness. A navaid being out of service is announced by NOTAM, not by a database update. Pair navaid lookups with the NOTAM endpoint and its navaid Q-codes, which decoding NOTAMs programmatically covers.
One firm rule: none of this is certified navigation data. It's good for displays, analysis and context. It isn't a substitute for current charts and NOTAMs for anyone actually flying.
The navaids endpoint is available from the free trial upwards, and because the data changes so slowly, a modest quota covers a lot once you cache it.
