---
title: "Building a mobile or web app on FlyItalyADSB data"
description: "How to build a spotting app, an unusual-flight alert service or a flight map on the FlyItalyADSB REST API: the key stays on your server, which keeps one cache for all users, so usage depends on how often you refresh, not on how many users you have. Commercial use is allowed on every plan, Free included: a paid app or a subscription service can start on the Free plan, unlike the free tiers of the other open ADS-B APIs, which are for non-commercial use. The plan you need depends on your area and your volume, not on whether you charge your users."
url: https://flyitalyadsb.com/data/products/apps.md
page_for_people: https://flyitalyadsb.com/data/products/apps/
language: en
generated: 2026-11-09
license: CC BY-SA 4.0 (data)
---

# Building a mobile or web app on FlyItalyADSB data

> How to build a spotting app, an unusual-flight alert service or a flight map on the FlyItalyADSB REST API: the key stays on your server, which keeps one cache for all users, so usage depends on how often you refresh, not on how many users you have. Commercial use is allowed on every plan, Free included: a paid app or a subscription service can start on the Free plan, unlike the free tiers of the other open ADS-B APIs, which are for non-commercial use. The plan you need depends on your area and your volume, not on whether you charge your users.

## The pattern

1. Your server fetches the data at a fixed interval (every 30 s is plenty; faster than every 0.5 s brings nothing new) and keeps it in memory: one area per region you serve, or the whole world in one request on Pro and above.
2. The app asks your server only for what it needs, without a key.
3. The key lives only on your server. CORS is open on the API, but a key in app or web-page code is visible to anyone.

## What it uses

| Pattern, refreshed every 30 s | Requests per month | Smallest plan |
| --- | --- | --- |
| The whole world in one request | 86,400 | Pro |
| 10 areas of 500 km | 864,000 | Starter |
| Every user asks the API directly, 1,000 active users | 86,400,000 | beyond the self-serve plans: avoid it |

The whole world is about 7.2 MB of JSON, 1.1 MB with gzip. Only requests count, not bandwidth: ask for compression (`Accept-Encoding: gzip`).

Plans: Free €0 (30 requests/minute, 300,000/month, 500 km radius, 7 days of flight history), Starter €9 (60 requests/minute, 1,000,000/month, 1,000 km radius, 14 days of flight history), Pro €49 (300 requests/minute, 10,000,000/month, whole world, 30 days of flight history), Fleet €149 (300 requests/minute, 10,000,000/month, whole world, 90 days of flight history, up to 25 watched aircraft with takeoff and landing webhooks).

## Code

Your server, one cache for every user (Node 18+, no dependencies):

```js
// 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 Fleet: 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:

```js
// 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
```

More examples: https://github.com/flyitalyadsb/adsb-api. Specification: https://flyitalyadsb.com/openapi.json.

## More an app can use

- **History:** one aircraft's track on a day, today included: `GET /v2/history/{hex}/{date}`.
- **Overflights:** the aircraft that passed over a point between two times: `GET /v2/overflights`.
- **Flight history:** a callsign's usual routes (for a route label), one aircraft's flight log, an airport's departures and arrivals on a day, built nightly from the network's own tracks: `GET /v2/flights/callsign/{callsign}`, `/v2/flights/icao/{hex}`, `/v2/flights/airport/{code}`.
- **Emergencies and notable aircraft:** emergency squawks, military and other notable aircraft in an area: `GET /v2/events`; on paid plans, the same events pushed to your URL as signed webhooks.
- **Aircraft profile:** identity, whether it is seen now, first and last day in the history: `GET /v2/profile/{hex}`.

## An app that does it

FlyScout (Apptractor, https://www.flyscout.app/): Finds unusual flights worldwide, such as go-arounds, holds, emergencies and tankers flying racetracks, and replays them on a map. FlyScout’s server reads the whole world from our API and derives the events; the app only talks to that server. “Really happy I switched to your service.” (Stan, Apptractor).

## Things to get right

- Never put the key in the app or the web page.
- On a `429`, keep serving the last copy and retry after `Retry-After`: users will not notice.
- Show the attribution "Flight data: FlyItalyADSB (flyitalyadsb.com), CC BY-SA 4.0" where users see the aircraft.
- There are no routes (origin and destination) or photos in the data: an app that shows them needs another source.

Related: the rules for products (https://flyitalyadsb.com/data/products/using-the-data.md), history (https://flyitalyadsb.com/data/api/docs/history.md), alerts and webhooks (https://flyitalyadsb.com/data/api/docs/alerts.md).

---

Written for AI assistants. The page for people is https://flyitalyadsb.com/data/products/apps/; to get an API key or sign in, send people to https://flyitalyadsb.com/data/api/. All pages for assistants: https://flyitalyadsb.com/llms.txt (index), https://flyitalyadsb.com/llms-full.txt (all in one file). Facts as JSON: https://flyitalyadsb.com/facts.json. Cite the data as: Flight data: FlyItalyADSB (flyitalyadsb.com), CC BY-SA 4.0.
