What the endpoint does
One request in. One settled record out.
A single GET takes a flight number and a date and returns the first complete answer. Internals stay ours.
What you get
One query shape
Flight number plus a date. Origin and destination are optional and only narrow the match when a number was flown more than once that day.
Sealed resolve
You send a number and a date. You get a settled record. How we produce it stays ours.
Delay math included
Arrival and departure delay in minutes, plus a disruption block that records how the number was derived and how much to trust it.
Settled records are frozen
A disruption older than three UTC days cannot change, so it is written to Postgres once and replayed from there at zero lookup latency.
Deterministic by design
Every timestamp is UTC. The same flight and date resolve the same way today and next quarter.
Auditable by default
Every call is logged against the key that made it, with latency, outcome and status code — so a disputed invoice or a runaway integration is a query, not a guess.
What settles
Only a complete delay, cancellation or diversion becomes the record. Anything else comes back as selected: null.
A flight that operated on time is not a record we freeze. Ask again and it costs another request. Coverage and limits.
The query
Two required parameters. Everything else narrows the match or changes how the answer is produced.
GET /api/v1/flights/status?flightNumber=BA2709&flightDate=2025-12-28| Parameter | Type | Notes |
|---|---|---|
| flightNumberRequired | string (2–12) | IATA flight designator. Trimmed and upper-cased server-side.also flight, number |
| flightDateRequired | YYYY-MM-DD | Scheduled departure date in UTC. A full ISO timestamp is truncated to the date.also date |
| origin | IATA (3 letters) | Disambiguates codeshares and repeated flight numbers.also originIata, from |
| destination | IATA (3 letters) | Disambiguates codeshares and repeated flight numbers.also destinationIata, dest, to |
| refresh | boolean flag | Skip the persisted record and resolve the flight again from scratch. |
| screenshot | boolean flag | Attach a rendered proof image of the record as base64. Never persisted.also includeScreenshot |
Boolean flags accept "1" · "0" · "true" · "false" · "yes" · "no".
Response shape
The same object comes back whether the answer was resolved live or replayed from storage.
- flightNumber
- Echoed back, normalised to upper case.string
- flightDate
- Echoed back as `YYYY-MM-DD`.string
- originIata
- Resolved departure airport.string | null
- destinationIata
- Resolved arrival airport.string | null
- selected
- The normalised record. Null when nothing was found.object | null
- flightDelay
- Delay and disruption verdict with its rationale, when timings were available.object | null
- meta
- Timing, cache and persistence state.object
Delay math, not just a status string
Minutes are signed the obvious way: positive is late, negative is early, zero is on time.
arrival
departure
Arrival delay is the headline number, because it is what most compensation and audit rules reference. On a cancellation it is null on purpose — a flight that never flew has no arrival to be late for.
rationale is the part worth reading. It carries the working — which clocks were compared, in what order — so a disputed number can be argued from the response rather than from memory. confidence tells you how much of that working rested on actual times rather than an estimate, and sources names the comparisons that produced it.
Status classification
Derived once from cancelled, diverted and arrival delay — never copied from a free-text status.
Arrival delay greater than zero minutes.
Never operated. delayMinutes is deliberately null.
Landed somewhere other than the filed destination.
Those three are the only outcomes that become a record, which is also why they are the only ones worth freezing. The union on selected.status also carries on_time and not_found; a lookup with nothing to settle expresses that as selected: null. The unmodified upstream status string is kept as selected.statusRaw.
Settled history is frozen
Caching here is a statement about the flight, not about traffic.
A disruption that old will not change again. It is written to Postgres once and replayed on every later request with meta.cached: true and durationMs: 0.
Never frozen. Status can still move while a flight is that recent, so those lookups go through a live resolve every time.
Bypasses the stored record and resolves from scratch. The fresh answer overwrites the record in place if it is still persistable.
On-time and not-found results are never stored, so they always cost a live resolve. meta.persisted tells you whether this particular call wrote anything.
One flight is enough to judge it.
Create an account, mint a key, and send the first request against a flight you already know the answer to.