Skip to content

Displays and devices

Desk displays, e-paper frames, LED panels, ESP32 and Raspberry Pi: how to show nearby aircraft without wasting requests, from one unit to a few thousand.

Two patterns, depending on how many units

Each device asks for itself

For a few units. It’s the simplest: the device asks for the aircraft in its radius. But the key sits in the firmware, where it can be extracted, and every extra device counts on the same key.

Your server for all of them

From a few dozen units up. Your server fetches once (your customers’ area, or the whole world on Pro) and each device asks the server only for the aircraft near it. Usage doesn’t grow with the units you sell and the key never leaves the server.

Your devices
ask your server for the aircraft nearby
Your server
one request every 30 s, keeps the copy in memory
FlyItalyADSB API
one key for the whole product

What one device uses

The data refreshes every 2 seconds; for a display 15–30 seconds is usually enough. Requests a month for a device asking for itself:

EveryPer month, per deviceDevices on the Free plan
5 s518,400none
15 s172,8001
30 s86,4003
60 s43,2006

With the server pattern there is one count only: one request every 30 seconds makes 86,400 requests a month, however many devices you have.

How big a response is

How many aircraft and bytes one request returns, in a few places:

AreaRadiusAircraftJSONWith gzip
Milan Malpensa15 km54 KB1 KB
London Heathrow50 km3733 KB7 KB
New York JFK50 km10563 KB10 KB
Frankfurt500 km662670 KB111 KB
Whole world—~11,0007.2 MB1.1 MB

Measured on 26 October 2026, 20:24 UTC. Evening in Europe: there are more aircraft during the day.

The code

Try it with curl (15 km around Heathrow)
curl --compressed -H "X-Api-Key: YOUR_KEY" \
  https://api.flyitalyadsb.com/v2/lat/51.47/lon/-0.45/dist/15
ESP32 asking for itself
// ESP32 (Arduino core 3.x) + ArduinoJson 7
// Aircraft within 15 km of the device, every 30 seconds.
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char* WIFI_SSID = "...";
const char* WIFI_PASS = "...";
const char* API_KEY = "YOUR_KEY";  // better: ask your own server, which holds the key
const char* URL = "https://api.flyitalyadsb.com/v2/lat/45.63/lon/8.72/dist/15";

void setup() {
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(500);
}

void loop() {
  WiFiClientSecure tls;
  tls.setInsecure();  // demo only: pin the root certificate in a product
  HTTPClient http;
  http.useHTTP10(true);  // no chunked encoding: ArduinoJson reads the stream
  http.begin(tls, URL);
  http.addHeader("X-Api-Key", API_KEY);
  http.setUserAgent("my-desk-display/1.0");
  int status = http.GET();
  if (status == 200) {
    JsonDocument filter;  // keep only the fields the display shows
    filter["ac"][0]["flight"] = true;
    filter["ac"][0]["t"] = true;
    filter["ac"][0]["alt_baro"] = true;
    JsonDocument doc;
    if (!deserializeJson(doc, http.getStream(), DeserializationOption::Filter(filter))) {
      for (JsonObject a : doc["ac"].as<JsonArray>()) {
        Serial.print(a["flight"] | "?");
        Serial.print("  ");
        Serial.print(a["t"] | "?");
        Serial.print("  ");
        if (a["alt_baro"].is<int>()) Serial.println(a["alt_baro"].as<int>());
        else Serial.println("ground");
      }
    }
  } else {
    Serial.printf("HTTP %d\n", status);  // 429: over the limit, retry later
  }
  http.end();
  delay(30000);  // every 30 s: 86,400 requests a month
}
Your server for all devices (Python)
# server.py: one request for all your devices (Python 3.9+, no dependencies)
import gzip, json, math, os, threading, time, urllib.request
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import parse_qs, urlparse

KEY = os.environ["FIA_API_KEY"]
# Pro and Business: the whole world. On Free use your area (up to 500 km).
SOURCE = "https://api.flyitalyadsb.com/v2/lat/0/lon/0/dist/20000"
EVERY_S = 30
snapshot = {"now": 0, "ac": []}

def refresh():
    while True:
        try:
            req = urllib.request.Request(SOURCE, headers={
                "X-Api-Key": KEY, "Accept-Encoding": "gzip", "User-Agent": "my-product/1.0"})
            with urllib.request.urlopen(req, timeout=30) as r:
                body = r.read()
                if r.headers.get("Content-Encoding") == "gzip":
                    body = gzip.decompress(body)
            snapshot.update(json.loads(body))
        except Exception as e:  # keep serving the last copy
            print("refresh failed:", e)
        time.sleep(EVERY_S)

def km(la1, lo1, la2, lo2):
    p = math.pi / 180
    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))

class Near(BaseHTTPRequestHandler):
    def do_GET(self):  # /near?lat=51.47&lon=-0.45&km=15
        q = {k: float(v[0]) for k, v in parse_qs(urlparse(self.path).query).items()}
        ac = [a for a in snapshot["ac"]
              if "lat" in a and km(q["lat"], q["lon"], a["lat"], a["lon"]) <= q.get("km", 15)]
        body = json.dumps({"now": snapshot["now"], "ac": ac}).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

threading.Thread(target=refresh, daemon=True).start()
ThreadingHTTPServer(("0.0.0.0", 8080), Near).serve_forever()

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

Watch out for

  • A key in firmware can be extracted. If you sell many units, keep it on your server.
  • A small radius on an ESP32. A 15 km response is a few KB; 50 km over New York is already 63 KB. Filter the fields as in the example, or let your server do it.
  • A User-Agent of your own on every request, and the X-Api-Key header.
  • On a 429, wait the Retry-After seconds instead of retrying straight away: refused requests don’t count, but repeating them doesn’t help.
  • Attribution on the device or in the manual and on the product page: the line to use.
  • The feeder perk (Pro limits for free) is for non-commercial use: it doesn’t cover units you sell.

Further reading

← For product builders