Read the data

Last updated

Two public reads return the same hexagon summary the map draws. Nothing here is a feed of raw aircraft positions.

The schema is OpenAPI 3.1. Every API response carries x-robots-tag so crawlers do not index the JSON, and the API host's robots.txt disallows every path. Search engines should rank this page, not the JSON.

Latest window

GET https://api.gpsinterference.com/v1/interference/latest

GET https://api.gpsinterference.com/interference.json is the same document. The map uses it when no day is selected. Both are the rolling window, normally 1440 minutes, rebuilt about every 3 hours while this repository is private. An hourly workflow exists and does not run on a private repository.

One UTC day

GET https://api.gpsinterference.com/v1/interference/YYYY-MM-DD

The last summary stored for that UTC day. The body is the latest document plus archive_date and complete. complete is false only while that UTC day is still today, because a later run may still replace it. A finished day is not recomputed: it is not a recount of only the flights that departed on that calendar date. Unknown and impossible dates return 404 and {"error":"no archive"}. Copies start from the day archiving was switched on.

GET https://api.gpsinterference.com/v1/interference/days returns the date index only: date, updated and hexes (a count). It does not include polygons.

Hexagon fields

FieldMeaning
hCell id, column_row on the Web Mercator hexagon grid.
gDistinct aircraft in the cell whose NACp stayed above 7.
bDistinct aircraft that reported NACp 7 or lower at least once in the cell.
pShare affected, in percent, to one decimal place: 100 × counted ÷ (g + b). Fewer than one low-accuracy aircraft counts as zero.
cg under 3%, y from 3% to 10%, r over 10%, taken from the rounded p.
polyRing of [latitude, longitude] pairs, in degrees.

Cells with fewer than 3 distinct aircraft are omitted. updated is UTC. window_minutes is the length of the window, 1440 in normal operation. sources lists the feeds that returned aircraft on the run which wrote that summary, which can be a subset of adsb.lol, adsb.fi, airplanes.live and adsb.one. polled_points, grid_points and failed_points describe that run's sample circles, not the whole 24-hour window.

Caching, CORS and limits

The worker sends cache-control: public, max-age=60 for the latest summary, for today's archive and for the date index. A frozen day is sent with max-age=86400. On 25 October 2026 the live zone in front of the API rewrote cache-control to max-age=14400 and replaced the worker's content-security-policy and X-Frame-Options. Until that override is removed, clients must not assume a 60-second cache. The robots tag that tells crawlers not to index API responses is set on every response and is not part of that override.

access-control-allow-origin is echoed only for https://gpsinterference.com and https://www.gpsinterference.com. curl, server-side fetches and any other non-browser client do not need CORS. A page on another website cannot read the JSON from the visitor's browser. There is no application rate limit. Cloudflare may still throttle a client; treat the cache headers as the way to avoid repeat downloads.

Attribution

Aircraft data from adsb.lol, adsb.fi, airplanes.live and adsb.one, shared under the Open Database License (ODbL). Cite https://gpsinterference.com. Low reported accuracy is not confirmed jamming and does not identify a source. Basemap tiles are separate and are not part of this API.

Examples

curl -sS "https://api.gpsinterference.com/v1/interference/latest"
curl -sS "https://api.gpsinterference.com/v1/interference/2026-10-25"

From a page on gpsinterference.com, or from server-side JavaScript:

const res = await fetch("https://api.gpsinterference.com/v1/interference/latest");
if (!res.ok) throw new Error(String(res.status));
const data = await res.json();
// data.hexes[i] is { h, g, b, p, c, poly }

A saved day is opened on the map. The calendar starts at the first day a copy was kept. How the counts are made: methodology.