An aircraft over the Atlantic works out where it is, says so out loud, and a few seconds later that position is a field in a JSON response on your laptop. Between those two moments sit six or seven distinct steps, each with its own failure modes, and understanding the chain explains most of the odd behaviour people hit when they start building on flight data.

This post follows one position the whole way. If you want the definition and the regulatory background, what is ADS-B covers that. If you want the bit-level detail of the broadcast itself, the anatomy of an ADS-B message takes a frame apart byte by byte. This one is the journey.

Step 1: The aircraft works out where it is

ADS-B stands for Automatic Dependent Surveillance–Broadcast, and the middle word carries more weight than the others. Dependent means the aircraft is not located by an external sensor the way primary radar locates it. It determines its own position from satellite navigation and reports that.

The onboard GNSS receiver computes latitude, longitude and a geometric altitude. Separately, the pressure altimeter gives a barometric altitude referenced to the standard 1013.25 hPa datum. Both matter later, and confusing them is the most common source of "the altitude is wrong" support tickets.

What can go wrong here: if the satellite signal is jammed, the aircraft loses its fix and has no position to broadcast. If it's spoofed, the aircraft computes a confident, precise, wrong position and broadcasts that — and nothing downstream can tell. The whole chain inherits the trust placed in the GNSS fix, which is why GPS jamming shows up so clearly in tracking data.

Step 2: The transponder broadcasts it

The transponder assembles that data into 112-bit messages and transmits them on 1090 MHz, roughly twice a second for position and velocity, every five seconds or so for the callsign.

No addressing, no encryption, no request. It's a shout, and anyone in range with a receiver can hear it. That openness is the reason a flight tracking industry exists at all.

Two details shape everything downstream. The broadcast is line of sight, so range scales with altitude — a rough rule is 1.23 × √(altitude in feet) nautical miles, putting an aircraft at FL350 within reach of a receiver about 230 nm away and a light aircraft at 1,000 feet within perhaps 30. And 1090 MHz is shared with Mode A/C replies, Mode S interrogation replies and TCAS, so in busy airspace transmissions collide and a meaningful fraction of frames are simply lost.

There is also a second link in the US: UAT on 978 MHz, used by many light aircraft below 18,000 feet. It's a completely different protocol, and a receiver built for 1090 does not hear it at all — the reason a slice of US general aviation is invisible to most feeds.

Step 3: A ground receiver hears it

Somewhere within line of sight, an antenna picks up the transmission. That receiver might be a professional installation, or — far more often — a hobbyist's setup on a roof feeding a network in exchange for access.

This is the step that determines coverage, and it's worth being blunt about what that means. There is no satellite blanket making ADS-B work everywhere. Coverage is the union of wherever people have put receivers, which correlates with population and hobbyist culture rather than with where aircraft fly. Western Europe is saturated. The mid-Atlantic has nothing, which is why transatlantic tracks go quiet in the middle unless a provider supplements with space-based reception.

What can go wrong here: nothing hears it. Not an error, just absence — and absence looks identical whether the aircraft landed, switched off, or flew somewhere unobserved.

A receiving antenna against the sky

Step 4: The message gets decoded

The receiver has 112 bits. Turning that into a position takes real work.

First, validate. Mode S uses a 24-bit CRC, and for the DF17 frames that carry ADS-B the parity is a straight checksum with nothing overlaid, so a corrupt frame can be identified and discarded. Given how much collision-induced corruption happens on a busy channel, this filter is doing constant work.

Then decode by message type. The first five bits of the payload say what kind of message it is: callsign, airborne position, surface position, velocity, or status.

Position is the awkward one. Latitude and longitude get 17 bits each, which isn't enough for global coordinates at useful precision. The protocol solves this with Compact Position Reporting, which transmits the position within a grid cell rather than absolutely, alternating between two slightly different grids on even and odd frames. Pair one of each and the absolute position falls out.

What can go wrong here: plenty. If you pair frames incorrectly you get a position exactly one grid cell away — 6° of latitude, or about 360 nautical miles. That's the signature of the classic "aircraft teleported across the country" bug, and it's why those jumps cluster at a specific distance rather than being random.

Step 5: Deduplication and fusion

In a well-covered area, forty receivers hear the same aircraft. The aggregator now has forty copies of roughly the same information, arriving at slightly different times with different reception quality, and has to decide which one is the position.

Strategies differ: take the most recent, take the one with the best reported navigational accuracy, take the nearest receiver, or weight several together. Aircraft that broadcast no position at all — older ones with a Mode S transponder but no ADS-B Out — can sometimes be located by multilateration, comparing arrival times across receivers, and that result has to be merged in too.

This step is invisible and consequential. It's the main reason two trackers show the same aircraft in slightly different places, which is unpacked in why flight data disagrees.

Step 6: Enrichment turns it into flight data

Here's where a position becomes useful, and it's the step people underestimate most.

What the aircraft broadcast is a 24-bit hex address, a callsign the crew typed, a position, an altitude and a velocity. What it did not broadcast is the registration, the aircraft type, the operator, the origin, the destination, or the schedule. None of that is in the protocol.

So the pipeline joins:

  • Hex → registration and type, from an aircraft registry that must be maintained as airframes change hands.
  • Callsign → operator, by matching the prefix against an airline table — reliable for scheduled carriers, unreliable for charters and wet leases.
  • Callsign → route and schedule, from an entirely separate source, since the aircraft says nothing about where it's going.

This is most of the work in a flight data product, and it's why raw feeds and enriched feeds are different categories of thing rather than different price points.

A Boeing 757 climbing away after sunset

Step 7: It arrives as JSON

At the end of the chain, one record:

{
  "icao24": "0A0022",
  "callsign": "DAH2055",
  "latitude": 51.274109,
  "longitude": -0.675964,
  "altitude": 9075.0,
  "ground_speed": 298.3,
  "track": 153.09,
  "vertical_rate": 2880.0,
  "is_on_ground": false,
  "last_seen": "2026-06-16T14:21:45.674566",
  "first_seen": "2026-06-16T14:17:09.312068",
  "registration": "7T-VJP",
  "aircraft_type": "Boeing B738",
  "airline": "Air Algerie"
}

Reading it with the chain in mind:

icao24 is the only field the aircraft truly asserts about its identity, and it's the one to key on. callsign is what the crew typed, which is usually but not always the flight number. altitude is barometric — pressure altitude on the standard datum, not height above sea level, and below the transition altitude it differs from true height by roughly 27 feet per hPa of local pressure deviation.

registration, aircraft_type and airline came from step 6 and can be null when the hex isn't in the registry or the callsign didn't resolve.

And last_seen is the most important field on the record, because it is the only thing telling you how old this is. A position is an observation with a timestamp, not a live feed. At 300 knots an aircraft covers about 0.08 nm per second, so a record 30 seconds old carries roughly 2.5 nm of positional uncertainty no matter how precise the coordinates look.

import requests
from datetime import datetime, timezone

BASE = "https://skylink-api.p.rapidapi.com"
H = {"X-RapidAPI-Key": KEY, "X-RapidAPI-Host": "skylink-api.p.rapidapi.com"}

r = requests.get(f"{BASE}/adsb/aircraft", headers=H,
                 params={"bbox": "51.2,-0.8,51.6,-0.2"})
payload = r.json()
snapshot = datetime.fromisoformat(payload["timestamp"]).replace(tzinfo=timezone.utc)

for a in payload["aircraft"][:5]:
    seen = datetime.fromisoformat(a["last_seen"]).replace(tzinfo=timezone.utc)
    age = (snapshot - seen).total_seconds()
    print(f"{a['icao24']} {a.get('callsign') or '':8} "
          f"{a.get('aircraft_type') or '?':16} age={age:5.1f}s")

Why the chain is worth knowing

Almost every confusing thing about flight data traces to a specific step.

An aircraft missing over the ocean is step 3 — nobody heard it. An aircraft that jumped 360 miles is step 4 — a decoding error with a quantised signature. Two trackers disagreeing by a mile is step 5 — different fusion choices. A null aircraft type is step 6 — the registry didn't have that hex. An altitude that seems several hundred feet off is step 1 — barometric versus geometric, and which one the display chose.

None of those are bugs in the sense of something being broken. They're properties of a system where aircraft volunteer their own positions over an open radio channel to whoever happens to be listening, which remains a slightly remarkable way to run global surveillance.

Getting the data

The ADS-B endpoint returns the end of this chain — decoded, deduplicated and registry-joined — filterable by bounding box, radius, altitude, speed, airline or registration. It's on the free trial, which is enough to pull live positions and see the structure for yourself.

If you'd rather run steps 3 and 4 yourself with a receiver on your own roof, that's a genuinely good way to understand the system, and build vs buy for ADS-B ingestion covers what it costs to take it further than a hobby.