{
  "openapi": "3.1.0",
  "info": {
    "title": "FlightRouteAPI",
    "version": "1.2.0",
    "description": "Worldwide aircraft observations, recorded history and upcoming flight schedules. Subscribe per tail number, without region selection. Only active subscribed registrations are collected. Observation availability and refresh latency vary; spatial queries search stored data."
  },
  "servers": [
    {
      "url": "/"
    }
  ],
  "security": [
    {
      "APIKey": []
    }
  ],
  "paths": {
    "/v1/aircraft": {
      "get": {
        "summary": "Fresh aircraft",
        "description": "Fresh positions only. Keyset pagination over a changing live dataset is not a consistent snapshot.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size; default 100",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "Previous next_after; results ordered by ICAO24",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{6}$"
            }
          },
          {
            "name": "tail",
            "in": "query",
            "required": false,
            "description": "Registration / tail number (case-insensitive)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bbox",
            "in": "query",
            "required": false,
            "description": "west,south,east,north. Split antimeridian queries. Example: -125,25,-66,50",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AircraftPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_aircraft"
      }
    },
    "/v1/aircraft/nearby": {
      "get": {
        "summary": "Aircraft within radius",
        "description": "PostGIS geography query in metres; ordered by ICAO24, not distance.",
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "description": "",
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            }
          },
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "description": "",
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            }
          },
          {
            "name": "radius_km",
            "in": "query",
            "required": true,
            "description": "",
            "schema": {
              "type": "number",
              "minimum": 0.1,
              "maximum": 500
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size; default 100",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          },
          {
            "name": "after",
            "in": "query",
            "required": false,
            "description": "Previous next_after; results ordered by ICAO24",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{6}$"
            }
          },
          {
            "name": "tail",
            "in": "query",
            "required": false,
            "description": "Registration / tail number (case-insensitive)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AircraftPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_aircraft_nearby"
      }
    },
    "/v1/aircraft/{icao}": {
      "get": {
        "summary": "Last known aircraft position",
        "description": "Includes stale positions retained in the database. 404 means no valid position has been observed.",
        "parameters": [
          {
            "name": "icao",
            "in": "path",
            "required": true,
            "description": "ICAO24 hex",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AircraftResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_aircraft_icao"
      }
    },
    "/v1/aircraft/{icao}/track": {
      "get": {
        "summary": "Recorded track history",
        "description": "Observed positions only; history begins at collection. Ordered by observed_at,icao24. Empty history is a successful empty page.",
        "parameters": [
          {
            "name": "icao",
            "in": "path",
            "required": true,
            "description": "ICAO24 hex",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Inclusive RFC3339 timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Exclusive RFC3339 timestamp; defaults to now. Maximum window 24h.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size; default 100",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque next_cursor from previous response. Reuse the same from/to bounds.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_aircraft_icao_track"
      }
    },
    "/v1/tails/{tail}/track": {
      "get": {
        "summary": "Recorded track history",
        "description": "Observed positions only; history begins at collection. Ordered by observed_at,icao24. Empty history is a successful empty page.",
        "parameters": [
          {
            "name": "tail",
            "in": "path",
            "required": true,
            "description": "Tail number; normalized to uppercase",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Inclusive RFC3339 timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Exclusive RFC3339 timestamp; defaults to now. Maximum window 24h.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size; default 100",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque next_cursor from previous response. Reuse the same from/to bounds.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackPage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_tails_tail_track"
      }
    },
    "/v1/subscriptions": {
      "get": {
        "summary": "List your tail subscriptions",
        "description": "",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Subscriptions"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_subscriptions"
      }
    },
    "/v1/subscriptions/{tail}": {
      "put": {
        "summary": "Subscribe to a tail number",
        "description": "Subscribe this API key to a registration worldwide. No region selection. Collection queue refreshes every 30 seconds; data appears only when observations are available. Limits count tail numbers per key. Collection is deduplicated across active keys.",
        "parameters": [
          {
            "name": "tail",
            "in": "path",
            "required": true,
            "description": "Tail number; normalized to uppercase",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tail": {
                      "type": "string"
                    },
                    "status": {
                      "const": "subscribed"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "put__v1_subscriptions_tail"
      },
      "delete": {
        "summary": "Remove your subscription",
        "description": "Idempotent; does not remove retained ADS-B history.",
        "parameters": [
          {
            "name": "tail",
            "in": "path",
            "required": true,
            "description": "Tail number; normalized to uppercase",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "delete__v1_subscriptions_tail"
      }
    },
    "/v1/subscriptions/positions": {
      "get": {
        "summary": "Last positions of your subscribed tails",
        "description": "Unknown tail: aircraft=null and stale=true. Contains only subscriptions belonging to this API key.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionPositions"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_subscriptions_positions"
      }
    },
    "/v1/status": {
      "get": {
        "summary": "Collector status",
        "description": "Database readiness is separate from data freshness. processed_observations counts processed points, including duplicates. last_success does not prove freshness of every subscribed aircraft.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_status"
      }
    },
    "/v1/ws/subscriptions": {
      "get": {
        "summary": "WebSocket subscription snapshots",
        "description": "X-API-Key required in handshake; no query-string credentials. Same-origin browser policy. Sends {type:\"snapshot\",data:[SubscriptionPosition],generated_at} immediately and every 10s. Each later snapshot consumes one request. Distributed connection limit, server ping/client pong, 1KB inbound limit, 1h maximum session. Reconnect with backoff. Subscription edits apply on next snapshot; collection has separate latency. No durable event replay. Browser clients need a backend proxy because browser WebSocket cannot set X-API-Key.",
        "parameters": [],
        "responses": {
          "101": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__v1_ws_subscriptions"
      }
    },
    "/healthz": {
      "get": {
        "summary": "Liveness",
        "description": "",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__healthz",
        "security": []
      }
    },
    "/readyz": {
      "get": {
        "summary": "PostgreSQL readiness",
        "description": "",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__readyz",
        "security": []
      }
    },
    "/openapi.json": {
      "get": {
        "summary": "OpenAPI specification",
        "description": "",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__openapi.json",
        "security": []
      }
    },
    "/docs": {
      "get": {
        "summary": "Self-contained API reference",
        "description": "",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, revoked or expired key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate, daily, subscription or stream limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds until retry when applicable. Absent for subscription cap.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable or capacity exhausted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "operationId": "get__docs",
        "security": []
      }
    },
    "/v1/subscriptions/plans": {
      "get": {
        "summary": "Public upcoming schedules for subscribed aircraft",
        "description": "Background job refreshes independently of requests. Priority FR24 \u2192 FlightAware \u2192 RadarBox; fallback also on no upcoming flights visible. No on-demand upstream calls. All times UTC. Schedule results have provider-specific provenance and do not inherit ADS-B ODbL attribution. Returned cached operations may be overdue/stale: inspect status, stale and scheduled/estimated times.",
        "operationId": "get_subscription_plans",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "after",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Previous next_after (tail number)"
          }
        ],
        "responses": {
          "200": {
            "description": "Cached public schedule snapshots, including source availability. Empty flights is not proof of no planned operations. Old cache survives failed refreshes and is marked stale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchedulePage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tail not subscribed by this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/tails/{tail}/plans": {
      "get": {
        "summary": "Public upcoming schedules for subscribed aircraft",
        "description": "Background job refreshes independently of requests. Priority FR24 \u2192 FlightAware \u2192 RadarBox; fallback also on no upcoming flights visible. No on-demand upstream calls. All times UTC. Schedule results have provider-specific provenance and do not inherit ADS-B ODbL attribution. Returned cached operations may be overdue/stale: inspect status, stale and scheduled/estimated times.",
        "operationId": "get_tail_plans",
        "parameters": [
          {
            "name": "tail",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Must be subscribed by this API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Cached public schedule snapshots, including source availability. Empty flights is not proof of no planned operations. Old cache survives failed refreshes and is marked stale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SchedulePage"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Tail not subscribed by this key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "API quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Storage unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/billing/config": {
      "get": {
        "summary": "Public monthly plans and Paddle client token",
        "tags": [
          "Billing"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          }
        }
      }
    },
    "/billing/accounts": {
      "post": {
        "summary": "Create a pending private billing/API key; shown once",
        "tags": [
          "Billing"
        ],
        "security": [],
        "responses": {
          "201": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          }
        }
      }
    },
    "/billing/account": {
      "get": {
        "summary": "Read billing state, including when API data access is suspended",
        "tags": [
          "Billing"
        ],
        "security": [
          {
            "APIKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          },
          "401": {
            "description": "Invalid, revoked or public demo key"
          }
        }
      }
    },
    "/billing/checkout": {
      "post": {
        "summary": "Create or reuse a Paddle transaction for a configured monthly plan",
        "tags": [
          "Billing"
        ],
        "security": [
          {
            "APIKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          },
          "401": {
            "description": "Invalid, revoked or public demo key"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan_id"
                ],
                "additionalProperties": false,
                "properties": {
                  "plan_id": {
                    "type": "string",
                    "examples": [
                      "starter",
                      "pro",
                      "business"
                    ]
                  }
                }
              }
            }
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "summary": "Create an authenticated Paddle customer portal session",
        "tags": [
          "Billing"
        ],
        "security": [
          {
            "APIKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          },
          "401": {
            "description": "Invalid, revoked or public demo key"
          }
        }
      }
    },
    "/webhooks/paddle": {
      "post": {
        "summary": "Receive signed Paddle subscription notifications",
        "tags": [
          "Billing"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "429": {
            "description": "Request limit exceeded"
          },
          "503": {
            "description": "Not configured or temporary error; retry webhooks"
          }
        },
        "parameters": [
          {
            "in": "header",
            "name": "Paddle-Signature",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "HMAC-SHA256 of ts:raw_body; timestamp tolerance 5 seconds"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event_id",
                  "event_type",
                  "data"
                ],
                "properties": {
                  "event_id": {
                    "type": "string"
                  },
                  "event_type": {
                    "type": "string"
                  },
                  "data": {
                    "type": "object"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/free-key": {
      "post": {
        "summary": "Create a private free API key",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "description": "Verified account session required. Creates or rotates the account free key: 1000 requests/day, 2/s, burst 5, 5 tails, 1 stream, 30-day initial expiry. Rotation preserves quotas, expiry and subscriptions. Maximum 3 generations per client IP/day. Free and public demo API traffic also share a per-IP 2/s, burst 5, 1000/day budget.",
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "api_key": {
                      "type": "string"
                    },
                    "daily_limit": {
                      "type": "integer"
                    },
                    "max_subscriptions": {
                      "type": "integer"
                    },
                    "rps": {
                      "type": "integer"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "IP creation quota reached"
          },
          "503": {
            "description": "Service temporarily unavailable"
          }
        }
      }
    },
    "/auth/request": {
      "post": {
        "summary": "Request a one-time email sign-in link",
        "description": "Request a one-time email sign-in link. Body: email and mode (register or login). Generic response prevents account enumeration. Rate limited by IP and mailbox.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request or expired token"
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "IP or mailbox limit exceeded"
          }
        }
      }
    },
    "/auth/verify": {
      "post": {
        "summary": "Consume a one-time 15-minute token, verify the account and set a 7-day HttpOnly session cookie",
        "description": "Consume a one-time 15-minute token, verify the account and set a 7-day HttpOnly session cookie. Body: token.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request or expired token"
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "IP or mailbox limit exceeded"
          }
        }
      }
    },
    "/auth/me": {
      "get": {
        "summary": "Read the verified session account email and has_key state",
        "description": "Read the verified session account email and has_key state.",
        "security": [
          {
            "SessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request or expired token"
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "IP or mailbox limit exceeded"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "summary": "Revoke the current session and clear the cookie",
        "description": "Revoke the current session and clear the cookie.",
        "security": [],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request or expired token"
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "IP or mailbox limit exceeded"
          }
        }
      }
    },
    "/v1/gnss/dates": {
      "get": {
        "summary": "List imported GNSS observation dates",
        "tags": [
          "GNSS"
        ],
        "description": "Daily navigation accuracy indicators, not confirmed jamming. Uses existing key/session quotas. No upstream requests on reads.",
        "responses": {
          "200": {
            "description": "Imported data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "format": "date"
                          },
                          "suspect": {
                            "type": "boolean"
                          },
                          "fetched_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "cell_count": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "Quota exceeded"
          },
          "503": {
            "description": "Storage unavailable"
          }
        }
      }
    },
    "/v1/gnss/cells": {
      "get": {
        "summary": "Read a daily GNSS H3 layer",
        "tags": [
          "GNSS"
        ],
        "description": "Daily navigation accuracy indicators, not confirmed jamming. Uses existing key/session quotas. No upstream requests on reads.",
        "responses": {
          "200": {
            "description": "Imported data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "suspect": {
                      "type": "boolean"
                    },
                    "fetched_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "resolution": {
                      "type": "integer",
                      "enum": [
                        4
                      ]
                    },
                    "notice": {
                      "type": "string"
                    },
                    "cells": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "h3",
                          "good",
                          "bad",
                          "percent"
                        ],
                        "properties": {
                          "h3": {
                            "type": "string",
                            "description": "H3 cell at resolution 4"
                          },
                          "good": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "bad": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "percent": {
                            "type": "number",
                            "minimum": 0,
                            "maximum": 100
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Authentication required"
          },
          "429": {
            "description": "Quota exceeded"
          },
          "503": {
            "description": "Storage unavailable"
          },
          "400": {
            "description": "Invalid date"
          },
          "404": {
            "description": "Dataset not imported"
          }
        },
        "parameters": [
          {
            "in": "query",
            "name": "date",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "UTC date; defaults to latest imported date"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "APIKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      },
      "SessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "fr_session"
      }
    },
    "schemas": {
      "Aircraft": {
        "type": "object",
        "required": [
          "icao24",
          "registration",
          "callsign",
          "aircraft_type",
          "lat",
          "lon",
          "altitude_ft",
          "on_ground",
          "ground_speed_kt",
          "track_deg",
          "vertical_rate_ft_min",
          "squawk",
          "observed_at"
        ],
        "properties": {
          "icao24": {
            "type": "string"
          },
          "registration": {
            "type": "string"
          },
          "callsign": {
            "type": "string"
          },
          "aircraft_type": {
            "type": "string"
          },
          "squawk": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "altitude_ft": {
            "type": [
              "number",
              "null"
            ]
          },
          "ground_speed_kt": {
            "type": [
              "number",
              "null"
            ]
          },
          "track_deg": {
            "type": [
              "number",
              "null"
            ]
          },
          "vertical_rate_ft_min": {
            "type": [
              "number",
              "null"
            ]
          },
          "on_ground": {
            "type": "boolean"
          },
          "observed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "Subscription": {
        "type": "object",
        "properties": {
          "tail": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SubscriptionPosition": {
        "type": "object",
        "properties": {
          "tail": {
            "type": "string"
          },
          "stale": {
            "type": "boolean"
          },
          "aircraft": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Aircraft"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "AircraftPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Aircraft"
            }
          },
          "next_after": {
            "type": "string"
          }
        }
      },
      "TrackPage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Aircraft"
            }
          },
          "next_cursor": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "format": "date-time"
          },
          "to": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Subscriptions": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Subscription"
            }
          }
        }
      },
      "SubscriptionPositions": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubscriptionPosition"
            }
          }
        }
      },
      "AircraftResult": {
        "type": "object",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Aircraft"
          },
          "stale": {
            "type": "boolean"
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "last_success": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "last_error": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "last_target": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "processed_observations": {
                  "type": "integer"
                }
              }
            }
          },
          "freshness_seconds": {
            "type": "number"
          },
          "collection_mode": {
            "type": "string",
            "const": "subscribed_tails"
          },
          "coverage": {
            "type": "string",
            "const": "worldwide"
          }
        }
      },
      "FlightPlan": {
        "type": "object",
        "required": [
          "id",
          "tail",
          "flight",
          "origin",
          "destination",
          "scheduled_departure",
          "status",
          "cancelled"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "tail": {
            "type": "string"
          },
          "flight": {
            "type": "string"
          },
          "origin": {
            "type": "string"
          },
          "destination": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "route": {
            "type": "string"
          },
          "scheduled_departure": {
            "type": "string",
            "format": "date-time"
          },
          "scheduled_arrival": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "estimated_departure": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "estimated_arrival": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled": {
            "type": "boolean"
          }
        }
      },
      "ScheduleAttempt": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "ok, no_upcoming_visible, access_denied, rate_limited, profile_not_found, unsupported_format, registration_not_confirmed, cooldown_or_budget, network_error or upstream_error"
          }
        }
      },
      "ScheduleSnapshot": {
        "type": "object",
        "properties": {
          "tail": {
            "type": "string"
          },
          "status": {
            "enum": [
              "pending",
              "ok",
              "no_upcoming_visible",
              "unavailable"
            ]
          },
          "last_checked": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_success": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_check": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "stale": {
            "type": "boolean"
          },
          "coverage": {
            "const": "public_page_partial"
          },
          "flights": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/FlightPlan"
            }
          }
        }
      },
      "SchedulePage": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ScheduleSnapshot"
            }
          },
          "next_after": {
            "type": "string"
          },
          "job_enabled": {
            "type": "boolean"
          },
          "notice": {
            "type": "string"
          }
        }
      }
    }
  }
}
