If your app has a "from" and a "to" airport, sooner or later you need the distance, a rough flight time, the time zone gap and maybe the CO2 per passenger, and you need it from a 3-letter code a user typed.
I needed exactly that for a small travel side project, so I built an Airport Database API. This post shows how to call it from Python and curl, with the real output I got today (2026-11-08), how to schedule a daily weather check, and what it does not do.
Why the usual approach hurts
The usual path looks like this:
- Download the OurAirports CSV (about 80,000 rows, most of them heliports and closed strips).
- Filter it, map IATA to ICAO, fix missing codes.
- Write a haversine function, then argue with yourself about what "flight time" means.
- Add a time zone library for the coordinates.
- For weather, learn the NOAA Aviation Weather API and decode METAR strings.
- Re-download the CSV every few weeks because airports change.
None of this is hard, but it is a day of glue code you then have to maintain. I wanted one call that takes ["SOF-VAR", "LHR-JFK"] and returns everything as JSON.
What the API does
Five modes, one endpoint:
| Mode | Input | Output |
|---|---|---|
distance | routes like JFK-LHR | km, miles, nautical miles, bearing, estimated flight time, time zone difference, CO2 per passenger (DEFRA 2025 factors) |
lookup | IATA or ICAO codes | name, city, country, coordinates, elevation, time zone, runways |
search | text, country, type | matching airports |
nearby | latitude, longitude, radius | airports sorted by distance |
weather | ICAO codes | latest METAR (raw and decoded), optional TAF |
Data comes from OurAirports (public domain), the official NOAA Aviation Weather Center API and the UK DEFRA 2025 conversion factors. No scraping of websites, no API key for the sources.
You can call it two ways:
- API.market (simple API key, free plan to try): Airport Database API on API.market
- Apify (pay per result, good for schedules): airports-flight-distance on Apify
Option 1: API.market with curl
Subscribe to the free plan on the API.market page, copy your key, then:
curl -X POST 'https://prod.api.market/api/v1/martindata/airportsflightdistan-api/m/airports-flight-distance' \ -H 'x-api-market-key: YOUR_API_MARKET_KEY' \ -H 'Content-Type: application/json' \ -d '{"mode": "distance", "routes": ["SOF-VAR", "LHR-JFK", "FRA-SIN"], "maxItems": 3}' The response is {"count": N, "items": [...]}. On API.market one request returns up to 100 items.
Option 2: Python through the Apify API
Get a token in Apify Console (Settings, Integrations). Then:
import os import requests TOKEN = os.environ["APIFY_TOKEN"] # your own token URL = ("https://api.apify.com/v2/acts/martin10291~airports-flight-distance/" "run-sync-get-dataset-items") def airports(payload): r = requests.post(URL, params={"token": TOKEN}, json=payload, timeout=120) r.raise_for_status() return r.json() routes = airports({"mode": "distance", "routes": ["SOF-VAR", "LHR-JFK", "FRA-SIN"]}) for r in routes: print(f"{r['route']:8} {r['greatCircleKm']:>8} km {r['estimatedFlightTime']:>7} " f"tz {r['timezoneDifferenceHours']:+}h CO2 {r['co2']['kgPerPassenger']} kg") This is what I got today. The run took 5.3 seconds:
SOF-VAR 363.5 km 0h 56m tz +0h CO2 51.8 kg LHR-JFK 5539.8 km 7h 10m tz -5h CO2 846.6 kg FRA-SIN 10280.2 km 12h 53m tz +7h CO2 1465.2 kg One full item looks like this (trimmed):
{ "route": "LHR-JFK", "from": {"iata": "LHR", "icao": "EGLL", "name": "London Heathrow Airport", "countryCode": "GB"}, "to": {"iata": "JFK", "icao": "KJFK", "name": "John F. Kennedy International Airport", "countryCode": "US"}, "greatCircleKm": 5539.8, "miles": 3442.3, "nauticalMiles": 2991.3, "initialBearingCompass": "WNW", "estimatedFlightTime": "7h 10m", "estimateAssumptions": "Estimate only: block time = distance / 830 km/h + 30 min taxi/climb/descent overhead. Not a schedule.", "timezoneDifferenceHours": -5.0, "co2": { "defraCategory": "long-haul", "kgPerPassenger": 846.6, "kgPerPassengerWithoutRF": 501.0, "includesRadiativeForcing": true } } The flight time is an estimate and says so in every row. I would rather ship an honest rough number than a fake schedule.
Nearby airports
I live in Varna, Bulgaria, so this was my first test: which airports with scheduled flights are within 150 km of the city center?
near = airports({"mode": "nearby", "latitude": 43.2141, "longitude": 27.9147, "radiusKm": 150, "maxItems": 5}) for a in near: print(a["iata"], a["name"], a["distanceKm"], "km", a["countryCode"]) Real output (2.8 seconds):
VAR Varna Airport 7.5 km BG BOJ Burgas Airport 78.7 km BG CND Mihail Kogălniceanu International Airport 135.7 km RO Only 3 rows, because by default nearby mode keeps airports with scheduled service. Set "scheduledServiceOnly": false if you also want small airfields.
Live METAR weather
wx = airports({"mode": "weather", "codes": ["LBWN", "EGLL"]}) for w in wx: print(w["icao"], w["flightCategory"], f"{w['temperatureC']}C", f"wind {w['windDirectionDeg']}/{w['windSpeedKt']}kt", "|", w["rawMetar"]) Today at 14:00-14:20 UTC:
EGLL MVFR 10C wind 310/4kt | METAR EGLL 081420Z COR AUTO 31004KT 250V340 7000 OVC012 10/07 Q1020 NOSIG LBWN VFR 8C wind 130/7kt | METAR LBWN 081400Z AUTO 13007KT 9999 NCD 08/M01 Q1030 NOSIG Each row has the raw METAR plus decoded fields (wind, gusts, visibility, temperature, dew point, pressure in hPa and inHg, clouds, flight category). Add "includeTaf": true for the forecast.
Schedule it: only changed weather
For a daily or hourly check you usually want only what changed. Turn on only-new mode and give the watch list a name:
{ "mode": "weather", "codes": ["LBWN", "LBBG", "LBSF"], "onlyNew": true, "monitorId": "bulgaria-airports" } Save that as a Task in Apify Console and add a schedule (for example every 30 minutes). Each run returns, and charges for, only the METARs that changed since the last run for that monitorId. Connect the dataset to Slack, email, Google Sheets or a webhook from the same screen, and you have a small weather alert feed with no server of your own.
In the other modes only-new returns airports or routes you have not seen before under that monitorId, which is useful if you sync a growing route list into your database.
Honest limits
- No live flights, timetables, delays or fares. It is airport data, distances and weather. If you need flight status, this is the wrong tool.
- Flight time is an estimate (distance / 830 km/h + 30 min). Real block times vary with wind, routing and aircraft.
- CO2 is a per-passenger estimate for a direct flight using DEFRA 2025 factors, not an airline- or aircraft-specific number.
- Small airfields often have no IATA code. Use the ICAO code or search mode. Codes not found are listed in the run summary and are not charged.
- Airport data is refreshed every 7 days from OurAirports, which is community maintained.
Price
- Apify: $1.00 per 1,000 results (one route, airport or weather report = one result). The three test runs above returned 8 results, less than one cent. Apify's free plan has a small monthly credit, enough to try it.
- API.market: a free plan to test, then the paid plans shown on the listing.
Wrap-up
I built this because I kept rewriting the same haversine plus CSV plus METAR code. If you run a travel site, a chatbot that answers "how far is X from Y", or a small ops dashboard, try the free plan on API.market or the Apify actor.
What field is missing for your use case? Runway details, more emission methods, something else? Tell me in the comments and I will look at it.