{
  "format": "1.0",
  "product": {
    "name": "Flight API",
    "tagline": "Historical flight status API",
    "description": "Query any past flight by number and date. Actual departure and arrival times, block time, aircraft and delay minutes — one REST endpoint, one settled record.",
    "baseUrl": "https://flight-api.dev",
    "documentation": "https://flight-api.dev/docs"
  },
  "authentication": {
    "type": "api-key",
    "keyPrefix": "flt_",
    "headers": [
      {
        "header": "Authorization",
        "value": "Bearer flt_…",
        "note": "Preferred."
      },
      {
        "header": "X-Api-Key",
        "value": "flt_…",
        "note": "Equivalent."
      },
      {
        "header": "X-Flight-Api-Key",
        "value": "flt_…",
        "note": "Legacy alias, still accepted."
      }
    ],
    "storage": "Keys are stored as a SHA-256 hash and compared in constant time. The plaintext is shown once, at creation."
  },
  "conventions": {
    "timezone": "UTC",
    "dateFormat": "YYYY-MM-DD",
    "timestampFormat": "ISO 8601 with a Z offset",
    "envelope": "Every response is JSON carrying a boolean `success` discriminator, with `data` on success and `message` on failure.",
    "settleWindowDays": 3,
    "booleanFlagValues": [
      "1",
      "0",
      "true",
      "false",
      "yes",
      "no"
    ]
  },
  "metering": {
    "rules": [
      {
        "label": "When it counts",
        "rule": "At authorisation, before the resolve runs. A miss, a cache replay and a full live resolve all cost exactly one credit."
      },
      {
        "label": "What does not count",
        "rule": "Rejected credentials (401), disabled keys and accounts (403), and GET /api/v1/account, which is deliberately unmetered so checking a balance never moves it."
      },
      {
        "label": "Validation failures",
        "rule": "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."
      },
      {
        "label": "At the ceiling",
        "rule": "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."
      },
      {
        "label": "Windows",
        "rule": "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."
      },
      {
        "label": "Billing the excess",
        "rule": "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."
      }
    ],
    "responseHeaders": [
      {
        "header": "X-Credits-Included",
        "type": "integer",
        "description": "Requests included in the current window. `unlimited` on internal accounts."
      },
      {
        "header": "X-Credits-Used",
        "type": "integer",
        "description": "Requests consumed so far in the current window, including this one."
      },
      {
        "header": "X-Credits-Remaining",
        "type": "integer",
        "description": "Requests left before the ceiling. Reaches zero one request before a 429."
      },
      {
        "header": "X-Credits-Overage",
        "type": "integer",
        "description": "Requests taken beyond the included allowance. Zero unless overage is enabled."
      },
      {
        "header": "X-Credits-Period-End",
        "type": "string (ISO 8601)",
        "description": "When the current window closes and the allowance resets."
      }
    ]
  },
  "endpoints": [
    {
      "id": "flight-status",
      "method": "GET",
      "path": "/api/v1/flights/status",
      "title": "Resolve a historical flight",
      "summary": "Resolves one flight on one date and returns the selected record. Settled disruptions are replayed from storage.",
      "auth": "api-key",
      "params": [
        {
          "name": "flightNumber",
          "aliases": [
            "flight",
            "number"
          ],
          "type": "string (2–12)",
          "required": true,
          "description": "IATA flight designator. Trimmed and upper-cased server-side."
        },
        {
          "name": "flightDate",
          "aliases": [
            "date"
          ],
          "type": "YYYY-MM-DD",
          "required": true,
          "description": "Scheduled departure date in UTC. A full ISO timestamp is truncated to the date."
        },
        {
          "name": "origin",
          "aliases": [
            "originIata",
            "from"
          ],
          "type": "IATA (3 letters)",
          "description": "Disambiguates codeshares and repeated flight numbers."
        },
        {
          "name": "destination",
          "aliases": [
            "destinationIata",
            "dest",
            "to"
          ],
          "type": "IATA (3 letters)",
          "description": "Disambiguates codeshares and repeated flight numbers."
        },
        {
          "name": "refresh",
          "type": "boolean flag",
          "description": "Skip the persisted record and resolve the flight again from scratch."
        },
        {
          "name": "screenshot",
          "aliases": [
            "includeScreenshot"
          ],
          "type": "boolean flag",
          "description": "Attach a rendered proof image of the record as base64. Never persisted."
        }
      ],
      "statuses": [
        {
          "code": 200,
          "meaning": "Resolved. `data.selected` may still be null when the flight was not found."
        },
        {
          "code": 400,
          "meaning": "Query validation failed. `message` names the offending field."
        },
        {
          "code": 401,
          "meaning": "Missing or invalid API key."
        },
        {
          "code": 402,
          "meaning": "No active plan on the account, or the subscription is canceled or past due. No credit is taken."
        },
        {
          "code": 403,
          "meaning": "Key or account disabled."
        },
        {
          "code": 429,
          "meaning": "Request allowance for the current window is exhausted and overage is not enabled. No credit is taken."
        },
        {
          "code": 500,
          "meaning": "Resolve failed. Safe to retry."
        }
      ],
      "url": "https://flight-api.dev/api/v1/flights/status"
    },
    {
      "id": "account",
      "method": "GET",
      "path": "/api/v1/account",
      "title": "Inspect the calling key",
      "summary": "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.",
      "auth": "api-key",
      "statuses": [
        {
          "code": 200,
          "meaning": "Key is valid."
        },
        {
          "code": 401,
          "meaning": "Missing or invalid API key."
        },
        {
          "code": 402,
          "meaning": "No active plan, or the subscription is canceled or past due."
        },
        {
          "code": 403,
          "meaning": "Key or account disabled."
        }
      ],
      "url": "https://flight-api.dev/api/v1/account"
    },
    {
      "id": "health",
      "method": "GET",
      "path": "/api/health",
      "title": "Liveness probe",
      "summary": "Unauthenticated. Returns immediately.",
      "auth": "none",
      "statuses": [
        {
          "code": 200,
          "meaning": "Service is up."
        }
      ],
      "url": "https://flight-api.dev/api/health"
    }
  ],
  "schemas": {
    "data": [
      {
        "name": "flightNumber",
        "type": "string",
        "description": "Echoed back, normalised to upper case."
      },
      {
        "name": "flightDate",
        "type": "string",
        "description": "Echoed back as `YYYY-MM-DD`."
      },
      {
        "name": "originIata",
        "type": "string | null",
        "description": "Resolved departure airport."
      },
      {
        "name": "destinationIata",
        "type": "string | null",
        "description": "Resolved arrival airport."
      },
      {
        "name": "selected",
        "type": "object | null",
        "description": "The normalised record. Null when nothing was found."
      },
      {
        "name": "flightDelay",
        "type": "object | null",
        "description": "Delay and disruption verdict with its rationale, when timings were available."
      },
      {
        "name": "meta",
        "type": "object",
        "description": "Timing, cache and persistence state."
      }
    ],
    "data.selected": [
      {
        "name": "found",
        "type": "boolean",
        "description": "The flight was matched."
      },
      {
        "name": "complete",
        "type": "boolean",
        "description": "Enough timings were present to settle the record."
      },
      {
        "name": "status",
        "type": "'delayed' | 'cancelled' | 'diverted' | 'on_time' | 'not_found'",
        "description": "Classified outcome."
      },
      {
        "name": "isDelayed",
        "type": "boolean",
        "description": "Arrival delay greater than zero."
      },
      {
        "name": "isCancelled",
        "type": "boolean",
        "description": "Flight never operated."
      },
      {
        "name": "isDiverted",
        "type": "boolean",
        "description": "Landed somewhere other than the destination."
      },
      {
        "name": "delayMinutes",
        "type": "number | null",
        "description": "Arrival delay in minutes."
      },
      {
        "name": "departureDelayMinutes",
        "type": "number | null",
        "description": "Departure delay in minutes."
      },
      {
        "name": "scheduledDeparture",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "scheduledArrival",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "estimatedDeparture",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "estimatedArrival",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "actualDeparture",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "actualArrival",
        "type": "string | null",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "airline",
        "type": "object | null",
        "description": "`name`, `iata`, `icao`."
      },
      {
        "name": "aircraft",
        "type": "object | null",
        "description": "`registration`, `iata`, `icao`, `icao24`, `label`."
      },
      {
        "name": "flightIata",
        "type": "string | null",
        "description": "Normalised IATA designator."
      },
      {
        "name": "flightIcao",
        "type": "string | null",
        "description": "Normalised ICAO designator."
      },
      {
        "name": "statusRaw",
        "type": "string | null",
        "description": "Raw status string, unmodified."
      }
    ],
    "data.meta": [
      {
        "name": "durationMs",
        "type": "number",
        "description": "Wall clock for the whole resolve. `0` on a cache hit."
      },
      {
        "name": "startedAt",
        "type": "string",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "finishedAt",
        "type": "string",
        "description": "ISO 8601, UTC."
      },
      {
        "name": "cached",
        "type": "boolean",
        "description": "Served from the persisted record instead of a live resolve."
      },
      {
        "name": "persisted",
        "type": "boolean",
        "description": "This response was written to the record table."
      },
      {
        "name": "recordId",
        "type": "string | null",
        "description": "Always null on the public endpoint."
      }
    ]
  },
  "caching": [
    {
      "label": "Persisted",
      "rule": "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."
    },
    {
      "label": "Never persisted",
      "rule": "On-time and not-found results, anything inside the three-day settle window, and screenshots."
    },
    {
      "label": "refresh=1",
      "rule": "Bypasses the stored record and resolves from scratch. The fresh answer overwrites the row in place when it is still persistable."
    }
  ],
  "resolverOutcomes": [
    {
      "value": "hit",
      "meaning": "A usable, complete record."
    },
    {
      "value": "incomplete",
      "meaning": "Matched the flight but lacked timings."
    },
    {
      "value": "miss",
      "meaning": "No record for this flight and date."
    },
    {
      "value": "skipped",
      "meaning": "Not needed for this lookup."
    },
    {
      "value": "error",
      "meaning": "The resolve failed. Safe to retry."
    }
  ],
  "examples": {
    "curl": "curl -sG \"https://flight-api.dev/api/v1/flights/status\" \\\n  -H \"Authorization: Bearer $FLIGHT_API_KEY\" \\\n  --data-urlencode \"flightNumber=BA249\" \\\n  --data-urlencode \"flightDate=2026-08-14\"",
    "typescript": "const params = new URLSearchParams({\n  flightNumber: 'BA249',\n  flightDate: '2026-08-14',\n})\n\nconst response = await fetch(`https://flight-api.dev/api/v1/flights/status?${params}`, {\n  headers: { Authorization: `Bearer ${process.env.FLIGHT_API_KEY}` },\n})\n\nconst { success, data, message } = await response.json()\nif (!success) throw new Error(message)\n\n// data.selected is the resolved record.\nconsole.log(data.selected?.status, data.selected?.delayMinutes, data.meta.cached)",
    "response": {
      "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": []
      }
    }
  },
  "artifacts": {
    "openapi": "https://flight-api.dev/openapi.json",
    "json": "https://flight-api.dev/api/docs",
    "llms": "https://flight-api.dev/llms.txt",
    "markdown": "https://flight-api.dev/docs.md"
  }
}