Skip to content

Mobile and web apps

Spotting apps, alerts for unusual flights, maps: the key stays on your server and the app asks you. That way usage depends on how often you refresh, not on how many users you have.

The pattern

Your server fetches the data at fixed intervals and keeps it in memory; the app asks the server only for what it needs. You can fetch one area per region you serve or, on Pro or Business, the whole world in one request.

Your app
asks your server, without a key
Your server
in-memory cache, refreshed every 30 s
FlyItalyADSB API
the key lives only here

What it uses

With the cache the count doesn’t depend on users. Two examples, refreshing every 30 seconds:

PatternPer monthPlan
The whole world in one request86,400Pro (only Pro and Business have a worldwide radius)
10 areas of 500 km864,000Starter (up to 1,000 km and 1 million a month)
Every user asks the API directly, 1,000 active users86,400,000beyond Business: avoid it

The whole world is about 7.2 MB of JSON, 1.1 MB with gzip: there are no bandwidth limits, only requests count. Ask for compression (Accept-Encoding: gzip; fetch does it by itself).

The code

Your server, one cache for everyone (Node 18+)
// server.mjs: one cache for all your app’s users (Node 18+, no dependencies)
import http from 'node:http';

const KEY = process.env.FIA_API_KEY;
// Pro and Business: the whole world. On Free use your area (up to 500 km).
const SOURCE = 'https://api.flyitalyadsb.com/v2/lat/0/lon/0/dist/20000';
const ATTRIBUTION = 'Flight data: FlyItalyADSB (flyitalyadsb.com), CC BY-SA 4.0';
let snapshot = { now: 0, ac: [] };

async function refresh() {
  try {
    const r = await fetch(SOURCE, { headers: { 'X-Api-Key': KEY, 'User-Agent': 'my-app-backend/1.0' } });
    if (r.ok) snapshot = await r.json(); // fetch handles gzip by itself
    else console.log('refresh', r.status, r.headers.get('retry-after') ?? '');
  } catch (e) {
    console.log('refresh failed', e.message); // the last copy stays
  }
}
refresh();
setInterval(refresh, 30_000);

const km = (la1, lo1, la2, lo2) => {
  const p = Math.PI / 180;
  const h = Math.sin(((la2 - la1) * p) / 2) ** 2
    + Math.cos(la1 * p) * Math.cos(la2 * p) * Math.sin(((lo2 - lo1) * p) / 2) ** 2;
  return 12742 * Math.asin(Math.sqrt(h));
};

// GET /near?lat=40.64&lon=-73.78&km=25
http.createServer((req, res) => {
  const q = new URL(req.url, 'http://localhost').searchParams;
  const lat = Number(q.get('lat')), lon = Number(q.get('lon'));
  const radius = Math.min(Number(q.get('km')) || 25, 250);
  const ac = snapshot.ac.filter((a) => a.lat != null && km(lat, lon, a.lat, a.lon) <= radius);
  res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'max-age=5' });
  res.end(JSON.stringify({ now: snapshot.now, attribution: ATTRIBUTION, ac }));
}).listen(8080);
In the app
// In the app: ask your server, never the API with the key
const res = await fetch('https://your-server.example/near?lat=40.64&lon=-73.78&km=25');
const { ac, attribution } = await res.json();
// show "attribution" where the user sees the aircraft

Replace YOUR_KEY with your key: it’s free, get it here. Full specification in openapi.json.

More for an app

  • History: one aircraft’s track on a day, today included, with /v2/history/{hex}/{date}. History.
  • Overflights: the aircraft that passed over a point between two times, with /v2/overflights.
  • Notable traffic: emergencies, military and other notable aircraft in an area with /v2/events, and on paid plans the same events pushed to your URL. Alerts and webhooks.
  • An aircraft’s profile in one request: identity, whether we see it now, first and last day in the history, with /v2/profile/{hex}.

Watch out for

  • Never put the key in the app or the web page. CORS is open, but a key in client code is visible to anyone.
  • Refreshing more often than every 2 seconds doesn’t help: the data doesn’t change faster.
  • Attribution in the app, where users see the aircraft: the line to use. The example server returns it with the data.
  • On a 429, keep the last copy and retry after Retry-After: users won’t notice.
  • There are no routes or photos in the data: if the app shows departure and arrival, you need another source.

Further reading

← For product builders