# air-routes.com API

Scheduled passenger **routes operating now**, with seasonality labels.
**Not** fares, **not** booking checkout, **not** date-specific schedules.
Provenance dumps and snapshot names are **not** part of the public contract.

Base URL: `https://air-routes.com` (local: `http://127.0.0.1:8422`)

All responses are JSON. No API key required.

| REST | MCP tool |
|------|----------|
| `GET /api/airports` | `resolve_airport` |
| `GET /api/connections` | `find_connections` |
| `GET /api/airport/{airport_code}/destinations` | `airport_destinations` |
| `GET /api/route/{route_id}` | `route_detail` |

MCP endpoint: `https://air-routes.com/mcp`

Also: [HTML](/developers) · [OpenAPI](/api/openapi.json) · [llms.txt](/llms.txt)

---

## Shared types

**Airport codes:** fields like `airport_code`, `from`, and `to` use the familiar
3-letter airport code (officially the IATA code — most travelers never need that name).

### Airport

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `airport_code` | string | 3-letter airport code (IATA), uppercase | `"LHR"` |
| `name` | string | airport name | `"London Heathrow Airport"` |
| `city` | string | city name | `"London"` |
| `country` | string | as stored in the snapshot | `"United Kingdom"` |
| `lat` | number\|null | WGS84 | `51.47` |
| `lon` | number\|null | WGS84 | `-0.46` |
| `importance` | integer | 0–5 hub weight | `5` |

### Booking

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `url` | string | canonical route page | `"https://air-routes.com/r/OTP-BCN"` |

### Airline

Used on connection **legs** and on **route detail** (one shape everywhere).

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `airline_code` | string | 2-letter airline code (IATA) | `"W6"` |
| `airline` | string | airline name | `"Wizz Air"` |
| `schedule` | object[] | operated weekdays with local departure times. Each entry has `day` (Mon…Sun) and `times` (`HH:MM[]`). Several times on the same day share one entry. When days are known but times are not, `times` is `[]` | `[{"day":"Wed","times":["09:40"]},{"day":"Sat","times":["18:15"]}]` |
| `seasonal_note` | string\|null | free-text seasonal caveat when present | `null` |
| `service_type` | string\|null | `"scheduled"`, `"charter"`, or `null` = unknown. **Charter** = capacity sold through tour operators; not bookable seat-only with the airline. Charter entries only ever appear with `include_charter=true` | `"scheduled"` |

**Charter flights are excluded by default, everywhere.** A carrier's charter
rotations are stripped from every airline list, a route flown *only* as
charter resolves nowhere (404 / not offered), and itineraries never route over
a charter leg. Pass `include_charter=true` (available on connections, airport
destinations, and route detail) to see charter entries, flagged via
`service_type`; a route detail then also carries `charter_only: boolean`.

### Leg

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `route_id` | string | `{FROM}-{TO}` | `"CLJ-IST"` |
| `from` | string | airport code | `"CLJ"` |
| `to` | string | airport code | `"IST"` |
| `flying_min` | integer | estimated block minutes | `105` |
| `seasonality_label` | string\|null | human seasonality label | `"ends October"` |
| `airlines` | Airline[] | carriers on this leg | `[{"airline_code":"TK","airline":"Turkish Airlines",…}]` |
| `booking` | Booking | canonical page for this leg | `{"url":"https://…/r/CLJ-IST"}` |

### Option

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `total_flying_min` | integer | sum of leg times (no layover math) | `425` |
| `stops` | integer | `legs.length - 1` | `1` |
| `legs` | Leg[] | ordered origin → destination | `[{…},{…}]` |

### Destination

Same public fields as a route detail (`/api/route/{route_id}`) — one direct
flight from the queried origin. Use this endpoint to pull an entire hub’s
schedule in one request instead of N× route detail.

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `route_id` | string | `{from}-{to}` | `"OTP-PMO"` |
| `from` | string | origin airport code | `"OTP"` |
| `to` | string | destination airport code | `"PMO"` |
| `status` | string | lifecycle status | `"active"` |
| `seasonality_label` | string\|null | | `"ends October"` |
| `distance_km` | number\|null | great-circle km | `1284.5` |
| `flying_min` | integer | | `150` |
| `airlines` | Airline[] | `schedule` included | `[{"airline_code":"FR","airline":"Ryanair",…}]` |
| `booking` | Booking | | `{"url":"https://air-routes.com/r/OTP-PMO"}` |

### Errors

| Status | Body | When | Example |
|--------|------|------|---------|
| 404 | `{"detail": string}` | unknown airport or route | `{"detail":"unknown airport XXX; try /api/airports?q=…"}` |
| 422 | validation error | missing/invalid query params | missing `from` |
| 429 | `{"detail": string}` + `Retry-After` | only if limits are re-enabled later | `Retry-After: 12` |

---

## 1. Resolve airports

`GET /api/airports` · MCP `resolve_airport`

| Input | Type | Required | Notes | Example |
|-------|------|----------|-------|---------|
| `q` | string | yes | city, airport name, or airport code; min length 1; max **15** matches | `"london"` |

**Response:** `{ "airports": Airport[] }`

```bash
curl 'https://air-routes.com/api/airports?q=london'
```

```json
{
  "airports": [
    {
      "airport_code": "LHR",
      "name": "London Heathrow Airport",
      "city": "London",
      "country": "United Kingdom",
      "lat": 51.47,
      "lon": -0.46,
      "importance": 5
    },
    {
      "airport_code": "LGW",
      "name": "London Gatwick Airport",
      "city": "London",
      "country": "United Kingdom",
      "lat": 51.15,
      "lon": -0.19,
      "importance": 4
    }
  ]
}
```

---

## 2. Find connections

`GET /api/connections` · MCP `find_connections`

| Input | Type | Required | Default | Notes | Example |
|-------|------|----------|---------|-------|---------|
| `from` | string | yes | — | origin airport code (uppercased server-side) | `"CLJ"` |
| `to` | string | yes | — | destination airport code | `"AMS"` |
| `max_stops` | integer | no | 2 | 0…3 inclusive | `1` |
| `max_flying_min` | integer | no | 1440 | must be > 0 | `1440` |
| `include_charter` | boolean | no | `false` | also offer a charter **direct** flight when one exists (never inside a multi-leg itinerary) | `true` |

Returns the fewest-stops tier that has any option — direct flights if any
exist, otherwise one-stop, otherwise two-stop (up to `max_stops`), **never a
mix**. Within that tier, options sort by `total_flying_min` (sum of leg block
times; no layover math). Layover tiers return at most 7 options; the direct
tier is not capped. Dropped routes are invisible; so are charter flights
unless `include_charter=true` (see the Airline shared type).
Unknown airport → 404 with a hint to use `/api/airports`.
Airport names and coordinates are **not** embedded here — resolve airport codes with `GET /api/airports?q=…`.

**Response**

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `from` | string | echoed airport code | `"CLJ"` |
| `to` | string | echoed airport code | `"AMS"` |
| `options` | Option[] | may be empty | `[{ "total_flying_min": 425, … }]` |

Public responses do **not** include a snapshot/dataset name.

```bash
curl 'https://air-routes.com/api/connections?from=CLJ&to=AMS&max_stops=1'
```

```json
{
  "from": "CLJ",
  "to": "AMS",
  "options": [
    {
      "total_flying_min": 425,
      "stops": 1,
      "legs": [
        {
          "route_id": "CLJ-IST",
          "from": "CLJ",
          "to": "IST",
          "flying_min": 105,
          "seasonality_label": null,
          "airlines": [
            {
              "airline_code": "TK",
              "airline": "Turkish Airlines",
              "schedule": [
                {"day": "Mon", "times": ["07:10"]},
                {"day": "Thu", "times": ["14:15"]},
                {"day": "Sat", "times": ["07:10"]}
              ],
              "seasonal_note": null,
              "service_type": "scheduled"
            }
          ],
          "booking": { "url": "https://air-routes.com/r/CLJ-IST" }
        },
        {
          "route_id": "IST-AMS",
          "from": "IST",
          "to": "AMS",
          "flying_min": 230,
          "seasonality_label": null,
          "airlines": [
            {
              "airline_code": "TK",
              "airline": "Turkish Airlines",
              "schedule": [
                {"day": "Mon", "times": ["07:10"]},
                {"day": "Thu", "times": ["14:15"]},
                {"day": "Sat", "times": ["07:10"]}
              ],
              "seasonal_note": null,
              "service_type": "scheduled"
            }
          ],
          "booking": { "url": "https://air-routes.com/r/IST-AMS" }
        }
      ]
    }
  ]
}
```

---

## 3. Airport destinations (direct only)

`GET /api/airport/{airport_code}/destinations` · MCP `airport_destinations`

| Input | Type | Required | Notes | Example |
|-------|------|----------|-------|---------|
| `airport_code` | string | yes | path; 3-letter airport code (IATA); uppercased; unknown → 404 | `"OTP"` |
| `include_charter` | boolean | no | query; default `false` — also list charter-only destinations and charter carrier entries, flagged via `service_type` | `true` |

**Response:** `{ "from": string, "from_city": string, "from_country": string, "destinations": Destination[] }`

Dropped routes excluded; charter flights too, unless `include_charter=true`. Each destination is full public route detail (airlines
with `schedule`, `flying_min`, booking URL) plus `from_city`,
`from_country`, `to_city`, `to_country` so agents can plan from one hub without
N× `/api/route` calls. The `*_city` fields carry the city a traveller searches
for — "London" for STN, "Puerto Plata" for POP — not the airport's own name and
not the raw administrative municipality.

Human-readable hub page: `/airport-routes-bucharest-OTP` (city slug + airport code).

```bash
curl 'https://air-routes.com/api/airport/OTP/destinations'
```

```json
{
  "from": "OTP",
  "from_city": "Bucharest",
  "from_country": "Romania",
  "destinations": [
    {
      "route_id": "OTP-BCN",
      "from": "OTP",
      "from_city": "Bucharest",
      "from_country": "Romania",
      "to": "BCN",
      "to_city": "Barcelona",
      "to_country": "Spain",
      "status": "active",
      "seasonality_label": null,
      "distance_km": 1970.0,
      "flying_min": 195,
      "airlines": [
        {
          "airline_code": "W6",
          "airline": "Wizz Air",
          "schedule": [
            {"day": "Mon", "times": ["06:15"]},
            {"day": "Wed", "times": ["12:40"]},
            {"day": "Fri", "times": ["06:15"]}
          ],
          "seasonal_note": null,
          "service_type": "scheduled"
        }
      ],
      "booking": { "url": "https://air-routes.com/r/OTP-BCN" }
    },
    {
      "route_id": "OTP-PMO",
      "from": "OTP",
      "to": "PMO",
      "status": "active",
      "seasonality_label": "ends October",
      "distance_km": 1284.5,
      "flying_min": 150,
      "airlines": [
        {
          "airline_code": "FR",
          "airline": "Ryanair",
          "schedule": [
            {"day": "Wed", "times": ["09:40"]},
            {"day": "Sat", "times": ["18:15"]}
          ],
          "seasonal_note": null,
          "service_type": "scheduled"
        }
      ],
      "booking": { "url": "https://air-routes.com/r/OTP-PMO" }
    }
  ]
}
```

---

## 4. Route detail

`GET /api/route/{route_id}` · MCP `route_detail`

| Input | Type | Required | Notes | Example |
|-------|------|----------|-------|---------|
| `route_id` | string | yes | `{FROM}-{TO}`; uppercased; unknown → 404 | `"OTP-PMO"` |
| `include_charter` | boolean | no | query; default `false` — resolve a charter-only route (otherwise 404) and list charter carrier entries; response then adds `charter_only: boolean` | `true` |

| Field | Type | Notes | Example |
|-------|------|-------|---------|
| `route_id` | string | | `"OTP-PMO"` |
| `from` | string | airport code | `"OTP"` |
| `to` | string | airport code | `"PMO"` |
| `status` | string | lifecycle status | `"active"` |
| `seasonality_label` | string\|null | | `"ends October"` |
| `distance_km` | number\|null | great-circle km | `1284.5` |
| `flying_min` | integer | | `150` |
| `airlines` | Airline[] | | `[{"airline_code":"FR","airline":"Ryanair",…}]` |
| `booking` | Booking | | `{"url":"https://air-routes.com/r/OTP-PMO"}` |

Public responses do **not** include evidence/`sources`.

```bash
curl 'https://air-routes.com/api/route/OTP-PMO'
```

```json
{
  "route_id": "OTP-PMO",
  "from": "OTP",
  "to": "PMO",
  "status": "active",
  "seasonality_label": "ends October",
  "distance_km": 1284.5,
  "flying_min": 150,
  "airlines": [
    {
      "airline_code": "FR",
      "airline": "Ryanair",
      "schedule": [
        {"day": "Wed", "times": ["09:40"]},
        {"day": "Sat", "times": ["18:15"]}
      ],
      "seasonal_note": null,
      "service_type": "scheduled"
    }
  ],
  "booking": { "url": "https://air-routes.com/r/OTP-PMO" }
}
```
