API reference

One endpoint, one response shape.

Authenticate with a key, pass a flight number and a date, read the resolved record. Everything below is generated from the route handlers themselves.

Base URL

https://flight-api.dev

All timestamps are ISO 8601 in UTC. Responses are always JSON and always carry a boolean success discriminator.

Machine-readable

Every artifact below is generated from the same source as this page, so it cannot describe a different API.

/openapi.json

Every path, parameter, schema and status code. Feed it to a client generator or an agent toolchain.

OpenAPI 3.1Download
/api/docs

The whole reference as one structured document, including the worked examples on this page.

/docs.md

The same reference as a single file, readable start to finish.

MarkdownDownload
/llms.txt

A short, link-first index for agents — start here and follow the links.

llms.txt
Give an agent the docs

No credential is needed to read any of these. Point an MCP client with fetch capability at the index and it can walk the rest itself.

mcp.json
{
  "mcpServers": {
    "fetch": {
      "command": "uvx",
      "args": [
        "mcp-server-fetch"
      ]
    }
  }
}

Then: “read https://flight-api.dev/llms.txt and use it to call the flight status endpoint”.

Authentication

Keys are hashed with SHA-256 and compared in constant time. The plaintext is shown once, at creation.

AuthorizationBearer flt_…Preferred.
X-Api-Keyflt_…Equivalent.
X-Flight-Api-Keyflt_…Legacy alias, still accepted.

Every key is prefixed flt_. Requests without a credential get a 401; a disabled key or account gets 403; an account with no active plan gets 402. Manage keys from the dashboard.

Metering

One credit per authorised request. The rules below are the whole contract — there is nothing else to model.

When it counts

At authorisation, before the resolve runs. A miss, a cache replay and a full live resolve all cost exactly one credit.

What does not count

Rejected credentials (401), disabled keys and accounts (403), and GET /api/v1/account, which is deliberately unmetered so checking a balance never moves it.

Validation failures

A 400 still costs a credit — the key was already authorised. The usage log records it against the same key so the ledger and the meter agree.

At the ceiling

Without overage the next request is refused with 429 and no credit is taken. With overage enabled the request is served and billed at the plan rate.

Windows

Allowances run in 30-day windows that roll from the previous window, not from the first request of the month. Unused credits do not carry over.

Billing the excess

Overage is metered as it happens and invoiced in arrears: one invoice at the start of each month covering the month before, charged automatically. A month without overage is not charged. The plan fee bills separately on its own cycle.

Response headers
X-Credits-IncludedintegerRequests included in the current window. `unlimited` on internal accounts.
X-Credits-UsedintegerRequests consumed so far in the current window, including this one.
X-Credits-RemainingintegerRequests left before the ceiling. Reaches zero one request before a 429.
X-Credits-OverageintegerRequests taken beyond the included allowance. Zero unless overage is enabled.
X-Credits-Period-Endstring (ISO 8601)When the current window closes and the allowance resets.
GET/api/v1/flights/statusAPI key

Resolve a historical flight

Resolves one flight on one date and returns the selected record. Settled disruptions are replayed from storage.

Query parameters
flightNumberstring (2–12)Required

IATA flight designator. Trimmed and upper-cased server-side.

aliases: flight, number

flightDateYYYY-MM-DDRequired

Scheduled departure date in UTC. A full ISO timestamp is truncated to the date.

aliases: date

originIATA (3 letters)

Disambiguates codeshares and repeated flight numbers.

aliases: originIata, from

destinationIATA (3 letters)

Disambiguates codeshares and repeated flight numbers.

aliases: destinationIata, dest, to

refreshboolean flag

Skip the persisted record and resolve the flight again from scratch.

screenshotboolean flag

Attach a rendered proof image of the record as base64. Never persisted.

aliases: includeScreenshot

Boolean flags accept "1" · "0" · "true" · "false" · "yes" · "no".

curl
curl -sG "https://flight-api.dev/api/v1/flights/status" \
  -H "Authorization: Bearer $FLIGHT_API_KEY" \
  --data-urlencode "flightNumber=BA249" \
  --data-urlencode "flightDate=2026-08-14"
TypeScript
const params = new URLSearchParams({
  flightNumber: 'BA249',
  flightDate: '2026-08-14',
})

const response = await fetch(`https://flight-api.dev/api/v1/flights/status?${params}`, {
  headers: { Authorization: `Bearer ${process.env.FLIGHT_API_KEY}` },
})

const { success, data, message } = await response.json()
if (!success) throw new Error(message)

// data.selected is the resolved record.
console.log(data.selected?.status, data.selected?.delayMinutes, data.meta.cached)

Response

A 200 always means the resolve completed — it does not guarantee the flight was found.

Top level (data)
flightNumber
string
Echoed back, normalised to upper case.
flightDate
string
Echoed back as `YYYY-MM-DD`.
originIata
string | null
Resolved departure airport.
destinationIata
string | null
Resolved arrival airport.
selected
object | null
The normalised record. Null when nothing was found.
flightDelay
object | null
Delay and disruption verdict with its rationale, when timings were available.
meta
object
Timing, cache and persistence state.
data.selected
found
boolean
The flight was matched.
complete
boolean
Enough timings were present to settle the record.
status
'delayed' | 'cancelled' | 'diverted' | 'on_time' | 'not_found'
Classified outcome.
isDelayed
boolean
Arrival delay greater than zero.
isCancelled
boolean
Flight never operated.
isDiverted
boolean
Landed somewhere other than the destination.
delayMinutes
number | null
Arrival delay in minutes.
departureDelayMinutes
number | null
Departure delay in minutes.
scheduledDeparture
string | null
ISO 8601, UTC.
scheduledArrival
string | null
ISO 8601, UTC.
estimatedDeparture
string | null
ISO 8601, UTC.
estimatedArrival
string | null
ISO 8601, UTC.
actualDeparture
string | null
ISO 8601, UTC.
actualArrival
string | null
ISO 8601, UTC.
airline
object | null
`name`, `iata`, `icao`.
aircraft
object | null
`registration`, `iata`, `icao`, `icao24`, `label`.
flightIata
string | null
Normalised IATA designator.
flightIcao
string | null
Normalised ICAO designator.
statusRaw
string | null
Raw status string, unmodified.
data.meta
durationMs
number
Wall clock for the whole resolve. `0` on a cache hit.
startedAt
string
ISO 8601, UTC.
finishedAt
string
ISO 8601, UTC.
cached
boolean
Served from the persisted record instead of a live resolve.
persisted
boolean
This response was written to the record table.
recordId
string | null
Always null on the public endpoint.
200 — cached disruption
{
  "success": true,
  "data": {
    "flightNumber": "BA249",
    "flightDate": "2026-08-14",
    "originIata": "LHR",
    "destinationIata": "GRU",
    "selected": {
      "found": true,
      "complete": true,
      "status": "delayed",
      "isDelayed": true,
      "isCancelled": false,
      "isDiverted": false,
      "delayMinutes": 47,
      "departureDelayMinutes": 39,
      "originIata": "LHR",
      "destinationIata": "GRU",
      "scheduledDeparture": "2026-08-14T21:40:00.000Z",
      "scheduledArrival": "2026-08-15T05:05:00.000Z",
      "estimatedDeparture": null,
      "estimatedArrival": null,
      "actualDeparture": "2026-08-14T22:19:00.000Z",
      "actualArrival": "2026-08-15T05:52:00.000Z",
      "airline": {
        "name": "British Airways",
        "iata": "BA",
        "icao": "BAW"
      },
      "aircraft": {
        "registration": "G-ZBKF",
        "iata": "789",
        "icao": "B789",
        "icao24": null,
        "label": "Boeing 787-9"
      },
      "flightIata": "BA249",
      "flightIcao": "BAW249",
      "source": null,
      "statusRaw": "Landed 05:52"
    },
    "flightDelay": {
      "delayMinutes": 47,
      "delayFormatted": "47 min late",
      "statusKind": "landed",
      "disruption": {
        "type": "delay",
        "isCancelled": false,
        "isDiverted": false,
        "neverArrived": false,
        "arrivalDelayMinutes": 47,
        "departureDelayMinutes": 39,
        "confidence": "high",
        "sources": [
          "ata_vs_sta"
        ],
        "rationale": [
          "Actual arrival 47 min after scheduled arrival"
        ]
      }
    },
    "meta": {
      "durationMs": 0,
      "startedAt": "2026-08-24T09:12:04.881Z",
      "finishedAt": "2026-08-24T09:12:04.881Z",
      "source": null,
      "cached": true,
      "persisted": true,
      "recordId": null
    },
    "sources": []
  }
}

Caching

Only outcomes that can no longer change are frozen, so a cached reply is never stale.

Persisted

Delayed, cancelled or diverted, complete, and departing more than three UTC days ago. Stored unique on flight number and date, then replayed with meta.cached true and durationMs 0.

Never persisted

On-time and not-found results, anything inside the three-day settle window, and screenshots.

refresh=1

Bypasses the stored record and resolves from scratch. The fresh answer overwrites the row in place when it is still persistable.

Errors

Failures carry a human-readable message; validation errors name the field.

Status codes
200
Resolved. `data.selected` may still be null when the flight was not found.
400
Query validation failed. `message` names the offending field.
401
Missing or invalid API key.
402
No active plan on the account, or the subscription is canceled or past due. No credit is taken.
403
Key or account disabled.
429
Request allowance for the current window is exhausted and overage is not enabled. No credit is taken.
500
Resolve failed. Safe to retry.
400 — validation
{
  "success": false,
  "message": "flightDate: flightDate must be YYYY-MM-DD"
}
GET/api/v1/accountAPI key

Inspect the calling key

Returns the account and key behind the supplied credential, its lifetime request count and the credit position for the current window. Unmetered — this call costs nothing.

200
{
  "success": true,
  "data": {
    "accountId": "acct_9f3c1d",
    "accountSlug": "acme-9f3c1d",
    "keyId": "clz8k2f9a0002qw2h",
    "keyName": "production",
    "keyPrefix": "flt_0Rk2Xa9c",
    "unlimited": false,
    "requestCount": 1284
  }
}
Status codes
200
Key is valid.
401
Missing or invalid API key.
402
No active plan, or the subscription is canceled or past due.
403
Key or account disabled.
GET/api/healthPublic

Liveness probe

Unauthenticated. Returns immediately.

200
{
  "ok": true,
  "service": "flight-api"
}