# Airport autocomplete

> Airport autocomplete for origin and destination inputs. No authentication required. Supports 30+ language locales.

Endpoint: `GET /api/v1/flights/airports`

Source: https://fx-port.com/docs/flights/airports

Airport autocomplete for origin and destination inputs. Accepts an IATA code, a city name or an airport name and returns matching airports and cities from a global database. **No authentication is required.**

**Permission:** Public (no API key). The two endpoints of this page are not counted by the key rate limits, but they are limited per IP address: debounce your typeahead.


> [!NOTE]
> Built for a debounced typeahead: wait about 300 ms after the last keystroke and cancel the request that is in flight. The answer is a flat **array**, not an object, and it has no `environment` key. It carries `Cache-Control: public, max-age=60, s-maxage=300`: a browser can reuse an answer for a minute and a CDN for five.


## Search airports

`GET /api/v1/flights/airports`

### Query parameters

| Parameter | Type | Required | Description |
|---|---|---|---|
| `query` | string | Yes | An IATA code (`ALG`), a city (`Paris`) or an airport name: 1 to 64 characters once cleaned (control characters are removed and runs of spaces become one). One letter is valid (a prefix of a code). |
| `locale` | string | No | The language of the names. Default `en`. Written `ll` or `ll-RR`; case and `_` or `-` are normalised (`FR` is `fr`, `es_ar` is `es-AR`) and anything else is read as `en`. See [Supported locales](#supported-locales). |

### Request examples

~~~bash
# IATA code lookup
curl "https://api.fx-port.com/api/v1/flights/airports?query=CDG&locale=en"

# City name, French names
curl "https://api.fx-port.com/api/v1/flights/airports?query=Paris&locale=fr"

# Arabic names
curl "https://api.fx-port.com/api/v1/flights/airports?query=ALG&locale=ar"
~~~

### Response example

The response is a flat **array** of records. A real record has about 40 keys (it is the answer of an airport search service, forwarded as it is); only the ones below are meant for your use. This is the answer for `query=ALG`:

~~~json
[
  {
    "ap": "ALG",
    "airportname": "Houari Boumediene",
    "cityname": "Algiers, Algeria",
    "cc": "DZ",
    "country": "Algeria",
    "timezone": "Africa/Algiers",
    "utc": "+01:00",
    "lat": 36.6985,
    "lng": 3.20672,
    "displayname": "Houari Boumediene, Algiers, Algeria, (ALG)",
    "shortdisplayname": "Algiers, Algeria (ALG)",
    "loctype": "ap",
    "cityonly": "Algiers",
    "isMetroOnly": false
  },
  {
    "ap": "AHO",
    "airportname": "Alghero-Fertilia",
    "cityname": "Alghero, Sardinia, Italy",
    "cc": "IT",
    "country": "Italy",
    "timezone": "Europe/Rome",
    "utc": "+02:00",
    "displayname": "Alghero-Fertilia, Alghero, Sardinia, Italy, (AHO)",
    "shortdisplayname": "Alghero, Sardinia, Italy (AHO)"
  }
]
~~~

### Key fields

| Field | Description |
|---|---|
| `ap` | The IATA code: use it as `origin` or `destination` in a search. |
| `airportname` | The airport name in the requested locale. |
| `cityname` | City and country in the requested locale. |
| `cityonly` | The city alone. |
| `cc` | ISO 3166-1 alpha-2 country code. |
| `country` | The country name. |
| `timezone` | The IANA time zone, for example `Europe/Paris`. |
| `utc` | The UTC offset at the time of the query. |
| `lat`, `lng` | Coordinates. |
| `displayname` | A full label for a result list. |
| `shortdisplayname` | A short label for the selected value. |
| `isMetroOnly` | `true` for a **city code** that stands for all the airports of a city (`PAR`, "All airports"): it is valid as an `origin` or `destination`. |

A query for `PAR` returns the city entry (`ap: "PAR"`, `isMetroOnly: true`) first, then `CDG`, `ORY`, `BVA`, `LBG` and other airports near Paris.

The other keys (identifiers, bounding boxes, images, search-form strings, display types ...) are raw data of the lookup service, mixed in lower case, camelCase and snake_case. They are not part of the API and can change: use only the fields above.

### Errors

| Status | Code | When |
|---|---|---|
| `422` | `invalid_query` | `query` is blank, or longer than 64 characters once cleaned. `errors[0].field` is `query`. |
| `422` | `validation_error` | `query` is missing (`errors[0].field` is `query`) or empty. |
| `429` | `rate_limit_exceeded` | Too many lookups from one IP address. Wait for the seconds in `Retry-After`. |
| `502` / `504` | | The lookup service failed (`502`) or timed out (`504`). Retry once after a moment. |

When the lookup service is down, the endpoint can answer `200` with up to 10 airports from a local list instead of an error.

## Top airports

`GET /api/v1/flights/top-airports` returns the most searched airports, for the first suggestions of an empty search box. No key is needed.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `airport_type` | string | No | `from` (default) for departure airports or `to` for arrival airports. |
| `locale` | string | No | The language of the names, read as for `search airports`. Default `en`. |
| `limit` | integer | No | How many airports, 1 to 10. Default 5. |
| `market_region` | string | No | A country or region code, to rank airports for one market. |

~~~bash
curl "https://api.fx-port.com/api/v1/flights/top-airports?airport_type=to&locale=fr&limit=2"
~~~

The answer is `{ "environment": "live", "airports": [...], "context": { "locale", "airport_type", "agency_id", "market_region" } }`. `context` echoes what the ranking used: `locale` and `market_region` (upper case; `null` when you sent none), and `agency_id`, an internal ranking scope of FX-Port's own apps that is `null` in your answers. Without a key `environment` is always `live`. Each airport has the fields of the table above plus `payload_locale`, `popularity_score`, `popularity_scopes` (`locale` and `global` scores), `recent_score` and `last_seen_day`. An invalid `airport_type` or a `limit` outside 1 to 10 is answered with `422`.

## Supported locales

Pass `locale=XX` in the query string.

| `locale` | Language |
|---|---|
| `en` | English (default) |
| `fr` | French |
| `ar` | Arabic |
| `de` | German |
| `es` | Spanish |
| `es-AR` | Spanish (Argentina) |
| `it` | Italian |
| `pt` | Portuguese |
| `nl` | Dutch |
| `nl-BE` | Dutch (Belgium) |
| `pl` | Polish |
| `cs` | Czech |
| `ru` | Russian |
| `uk` | Ukrainian |
| `el` | Greek |
| `ro` | Romanian |
| `da` | Danish |
| `no` | Norwegian |
| `sv` | Swedish |
| `fi` | Finnish |
| `tr` | Turkish |
| `he` | Hebrew |
| `ko` | Korean |
| `ja` | Japanese |
| `zh-CN` | Chinese (Simplified) |
| `zh-TW` | Chinese (Traditional) |
| `th` | Thai |
| `id` | Indonesian |
| `ms` | Malay |
| `vi` | Vietnamese |
| `en-PH` | English (Philippines) |

The locale changes the names of airports and cities. The IATA code (`ap`) and the country code (`cc`) are always the same. A well-formed value that is not in the list (`xx`) is **not rejected**: it is passed on to the lookup service, and in our tests the names came back in English.

## Related

- [Search flights](https://fx-port.com/docs/flights/search.md): the next step, with the `ap` code.
