A flight status API takes a flight number and tells you what that flight is doing today: whether it's on time, delayed, boarding, airborne, landed or cancelled, and its times, terminal, gate and baggage belt. Travel apps, airport displays, chauffeur and hotel pickup services, and corporate travel tools are built on it.
This guide is for choosing and integrating one. It covers what the data looks like, how to call it from Python and Node, how to work out delays correctly, when to poll and when to use webhooks, what's available for free, and how the main providers compare on price. If you want a complete build, our delay notification tutorial walks through one end to end.
What a flight status response contains
Here's the response from SkyLink's flight status endpoint for a London–New York flight:
{
"flight_number": "BA 123",
"airline": "British Airways",
"status": "En Route",
"departure": {
"airport": "EGLL",
"airport_full": "London Heathrow Airport",
"scheduled_time": "10:30",
"scheduled_date": "11 Feb",
"actual_time": "10:35",
"actual_date": "11 Feb",
"terminal": "5",
"gate": "A12",
"checkin": ""
},
"arrival": {
"airport": "KJFK",
"airport_full": "John F Kennedy International Airport",
"scheduled_time": "14:45",
"scheduled_date": "11 Feb",
"estimated_time": "14:50",
"estimated_date": "11 Feb",
"terminal": "7",
"gate": "B15",
"baggage": ""
}
}Most flight status APIs return the same core information, under different field names:
| What you need | Field here | Notes |
|---|---|---|
| Current state | status | Free text such as "En Route" or "Landed". Not a fixed list |
| Scheduled times | scheduled_time, scheduled_date | Local time at each airport |
| Latest times | actual_* (departure), estimated_* (arrival) | Where the delay comes from |
| Terminal and gate | terminal, gate | Often empty until close to departure |
| Check-in and baggage | checkin, baggage | Published late, sometimes not at all |
Two details trip people up. Unpublished values are empty strings, not null. A gate of "" means "not announced yet", so show a dash rather than an error. And times are local to each airport, as on a departure board. That's what passengers expect to see, but it means you can't subtract a departure time from an arrival time without time zones.
Calling it from Python
import re
import requests
BASE = "https://skylink-api.p.rapidapi.com"
H = {"X-RapidAPI-Key": KEY, "X-RapidAPI-Host": "skylink-api.p.rapidapi.com"}
def flight_status(number):
"""Today's status for a flight number like BA117 or BAW117. None if not operating."""
number = re.sub(r"\s+", "", number).upper()
r = requests.get(f"{BASE}/flight_status/{number}", headers=H, timeout=(5, 15))
if r.status_code == 404:
return None # not operating today, or not published yet
r.raise_for_status()
return r.json()The endpoint accepts IATA flight numbers (BA117) and ICAO-style ones (BAW117). The docs recommend IATA when you have it, because ICAO prefixes are resolved through reference data and that's less reliable for some European operators. Strip spaces, because users type BA 117 and boards print it that way.
A 404 usually means the flight isn't operating today or hasn't been published yet. There's no date parameter: flight status means today's flight. To check another day, use the schedules endpoint, which covers five days back to one day ahead for a whole airport.
Calling it from Node
The same call in JavaScript, which must run on your server so the key never reaches a browser:
// Server-side only: never ship your API key to the browser.
export async function flightStatus(number) {
const id = number.replace(/\s+/g, "").toUpperCase();
const res = await fetch(`https://skylink-api.p.rapidapi.com/flight_status/${encodeURIComponent(id)}`, {
headers: {
"X-RapidAPI-Key": process.env.SKYLINK_API_KEY,
"X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
},
signal: AbortSignal.timeout(15_000),
});
if (res.status === 404) return null; // not operating today, or not published yet
if (!res.ok) throw new Error(`flight status failed: ${res.status}`);
return res.json();
}If you're buying directly rather than through RapidAPI, the base URL is https://data.skylinkapi.com/v3.1 and the only header is x-api-key. RapidAPI vs direct explains the difference.

Working out the delay
Most products show "delayed 25 minutes" rather than raw times. The delay is the difference between the scheduled time and the latest time at the same airport, so it doesn't need time zones. Dates do matter, though: a flight due at 23:50 that leaves at 01:05 is 75 minutes late, not 22 hours early.
from datetime import datetime
def _when(date_label, time_label, year):
"""Board-style local labels ('11 Feb', '10:35') to a naive local datetime."""
if not date_label or not time_label:
return None
try:
return datetime.strptime(f"{date_label} {year} {time_label}", "%d %b %Y %H:%M")
except ValueError:
return None
def delay_minutes(leg, latest="actual", year=None):
"""Minutes late (negative = early). Both times are local to the same airport."""
year = year or datetime.now().year
sched = _when(leg.get("scheduled_date"), leg.get("scheduled_time"), year)
other = _when(leg.get(f"{latest}_date"), leg.get(f"{latest}_time"), year)
if not sched or not other:
return None
mins = (other - sched).total_seconds() / 60
if mins < -12 * 60: # e.g. scheduled 31 Dec, departed 1 Jan
mins += 365 * 24 * 60
return round(mins)Then build a display card that handles the empty fields:
def card(s):
dep, arr = s["departure"], s["arrival"]
blank = lambda v: v or "—" # empty strings mean "not published yet"
return {
"flight": s["flight_number"],
"airline": s["airline"],
"status": s["status"],
"from": f"{dep['airport_full']} ({dep['airport']})",
"to": f"{arr['airport_full']} ({arr['airport']})",
"departure_delay": delay_minutes(dep, "actual"),
"arrival_delay": delay_minutes(arr, "estimated"),
"terminal_gate": f"T{blank(dep['terminal'])} / gate {blank(dep['gate'])}",
"baggage": blank(arr["baggage"]),
"cancelled": "cancel" in s["status"].lower(),
}For the example above, that gives a departure delay of 5 minutes and an estimated arrival 5 minutes late. Don't hard-code a list of status values. The docs describe status as display text, so match loosely, as the cancelled check does, and only special-case what your interface needs.
In the US, a flight counts as on time if it arrives within 15 minutes of schedule. That's a sensible threshold for colouring a card amber.
Polling or webhooks?
Polling means calling the endpoint on a timer. It's simple and right for on-demand lookups: a user types a flight number and you show the result. For a flight someone is actively watching, the docs suggest polling every two to five minutes. More often than that rarely shows anything new, because the underlying times don't change by the second.
Webhooks turn it around: you subscribe to a flight number and SkyLink sends your server a POST when something changes. The events are flight_delayed, flight_cancelled, flight_boarding, flight_landed, gate_changed and a catch-all status_changed. Status is checked every five minutes. Webhooks come with the Pro plan (one webhook) and Business (ten). They're the better fit for "tell me if anything changes" features, and webhooks vs polling goes into the trade-offs.
To get a sense of polling cost, tracking one flight every five minutes from check-in to landing on a long-haul trip is about 100 requests. Basic's 5,000 a month covers roughly 50 such trips. Short-haul flights cost much less.
Free flight status APIs
People often search for a free flight status API, so here's the honest picture in October 2026:
- SkyLink has a free trial of 1,000 requests a month, by application, for personal and non-commercial projects.
- AviationStack has a free plan of 100 requests a month, with some features reserved for paid plans.
- FlightAware AeroAPI has a Personal tier with no monthly minimum: it bills per query, includes up to $5 of free usage a month, and is licensed for personal or academic use.
All three are fine for prototypes, hobby projects and learning. For a commercial product, you'll need a paid plan with any provider. Free tiers also tend to have strict rate limits; SkyLink's trial allows one request per second. Free flight tracking APIs compares free options more widely.
Comparing providers
Prices from each provider's public pricing as of October 2026. They change, so check before you decide.
| Provider | Entry paid plan | Pricing model | Notes |
|---|---|---|---|
| SkyLink API | $19/mo for 5,000 requests; Pro $59/mo for 20,000 | Flat monthly tiers, yearly billing cheaper | Status, schedules, live ADS-B, weather and NOTAMs on one key; webhooks on Pro |
| AviationStack | $49.99/mo for 10,000 requests | Flat monthly tiers | Popular for status and schedules; live positions limited |
| FlightAware AeroAPI | Standard from a $100/mo minimum | Per query | High-confidence US data and Foresight predictions; costs scale with polling |
| AirLabs | Free and paid tiers | Flat monthly tiers | Status plus live positions and route data |
| Cirium | Contact sales | Enterprise contracts | Deep schedule and historical data for large organisations |
Questions that matter more than the headline price:
How does cost grow with polling? Per-query pricing looks cheap until you poll every few minutes. Work out your monthly requests before you compare.
Is the commercial licence included? Some cheap or free tiers exclude commercial use. All of SkyLink's paid plans include a commercial licence.
What else will you need? Most flight status products soon want live positions for a map, airport data for names and coordinates, and weather for the delay explanation. Buying them from one provider means one key, one bill and consistent airport codes.
What are the rate limits? On SkyLink, Basic allows two requests a second and Pro ten. That matters if you refresh many flights at once.
Side-by-side pages go into more detail: AviationStack vs SkyLink, FlightAware vs SkyLink and AirLabs vs SkyLink.

Integration checklist
Before you ship:
- Normalise flight numbers. Strip spaces, uppercase, prefer IATA.
- Treat a 404 as "not today", not as an error. Point users to the schedule for other dates.
- Show empty fields as dashes. Gates and belts appear late.
- Compute delays per airport, with the date included.
- Match status text loosely. No fixed list.
- Cache briefly. Two to five minutes per flight is plenty, and it protects your quota when many users watch the same flight.
- Keep the key on your server. Call the API from your backend, never from browser or mobile code.
- Handle
429. Back off and retry. It means you've hit your per-second or monthly limit. - Watch codeshares. One aircraft can carry several flight numbers. Look up the operating carrier's number when you can; codeshare flights explained covers why.
Getting started
The Flight Operations page shows the status, schedules and webhook endpoints together, and the flight status docs have the full field reference. To experiment, apply for the free trial. To build a product, the pricing page has the plans, starting at $19 a month, or $16 a month billed yearly.
