# API reference

The base URL is `https://api.onde.earth`. All requests use HTTPS and return JSON. Send live requests in [City API](/#api) on the landing page.

A successful response has `data` and `meta`. `data` is one object or a list. `meta.dataset` is the dataset version. [Errors](/docs#errors) gives the format of an error.

## Quick start

[Sign in](/sign-in). On the dashboard, create a key. Put the key in `ONDE_KEY`. Send the request.

If you distribute the data, keep this credit: Contains data from GeoNames, CC BY 4.0, modified.

```bash
curl "https://api.onde.earth/v1/cities/autocomplete?q=kyoto" \
  -H "Authorization: Bearer $ONDE_KEY"
```

## Authentication

A secret key starts with `onde_sk_`. Send the secret key from a server, in the `Authorization` header. The API refuses a secret key in the URL.

A public key starts with `onde_pk_`. Use a public key in a browser. Send the public key in `?key=`, because an `Authorization` header adds an `OPTIONS` request for each new URL. The API accepts a public key only from the allowed origins of the key.

## Rate limits

[Each plan](/#plans) sets a monthly limit of units and a limit of requests per second. A unit is a response with status 200 from any endpoint. A reverse lookup that finds no city also counts one unit. A batch reverse lookup counts one unit for each position, and one request for the limit of requests per second. If the positions are more than the units left in the month, the API refuses the full batch.

A public key also has a limit of 10 requests per second for each IP network. If the requests are above a limit, the API sends status 429 and a `Retry-After` header. The `Retry-After` value is in seconds.

## Errors

An error response has the content type `application/problem+json`. The body has `code`, `detail`, and `requestId`.

| Code | Status | Meaning |
| --- | --- | --- |
| `invalid_request` | 400 | A parameter or the body is not valid. The `errors` field lists each parameter that is not valid. |
| `unauthorized` | 401 | The request has no key, the key is not valid, or a secret key is in the URL. |
| `origin_not_allowed` | 403 | The origin of the request is not one of the allowed origins of the public key. |
| `not_found` | 404 | The city ID or the country code does not exist. |
| `city_retired` | 410 | The city ID is retired and has no replacement. |
| `rate_limited` | 429 | The requests are above a limit of requests per second. |
| `quota_exceeded` | 429 | The account is above its monthly limit of units. |
| `internal_error` | 500 | An error occurred on the server. |

## City API

City API gives autocomplete, search, and reverse lookup for cities, with names in 13 languages.

### Cities

While a person types, use [autocomplete](/docs#autocompleteCities). Then send the ID of the selected city to [Get a city](/docs#getCity). For a full name, such as `Burlington, Vermont`, use [search](/docs#searchCities). To refuse a guess, accept only a city with `exact` or `alias` in `match`.

- Autocomplete: `GET /v1/cities/autocomplete`
- Search: `GET /v1/cities/search`
- Reverse lookup: `GET /v1/cities/reverse`
- Batch reverse lookup: `POST /v1/cities/reverse/batch`
- Get a city: `GET /v1/cities/{id}`

#### The city object

Autocomplete, search, reverse lookup, and batch reverse lookup return the city object. [Get a city](/docs#getCity) returns the full city object. The full city object adds the fields with the full city label, such as the names in each language.

Fields:

- `id` (string): The city ID. The ID never changes.
- `name` (string): The display name in the requested language.
- `localName` (string or null): The name in the main language of the country, such as München, or null.
- `type` (string): The place type: city, or district for a part of a larger city.
- `parentCityId` (string or null): The ID of the parent city of a district, otherwise null.
- `country` (object): The country of the city.
  - `code` (string): The ISO 3166-1 alpha-2 code.
  - `name` (string): The name in the requested language.
  - `names` (object, full city): The country name in each language, by language code.
- `region` (object or null): The region, which is the first-order division of the country, or null.
  - `id` (string): The value for the `region` parameter of search.
  - `isoCode` (string or null): The ISO 3166-2 code, or null.
  - `name` (string): The name in the requested language.
  - `names` (object, full city): The region name in each language, by language code.
- `coordinates` (object): The position of the city.
  - `latitude` (number): The latitude in degrees.
  - `longitude` (number): The longitude in degrees.
- `population` (integer): The population in the dataset.
- `timeZone` (string): The IANA time zone of the city.
- `isCapital` (boolean): True for a national capital.
- `disputed` (boolean): True if the city is in an area on a published list of disputed areas. The value is not a political position.
- `names` (object, full city): The name in each language, by language code.

```json
{
  "id": "city_f56eq77e1f",
  "name": "Paris",
  "localName": "Paris",
  "type": "city",
  "parentCityId": null,
  "country": {
    "code": "FR",
    "name": "France",
    "names": {
      "en": "France",
      "de": "Frankreich",
      "es": "Francia",
      "fr": "France",
      "it": "Francia",
      "nl": "Frankrijk",
      "pt": "França",
      "ru": "Франция",
      "ja": "フランス",
      "ko": "프랑스",
      "zh": "法国",
      "ar": "فرنسا",
      "hi": "फ़्रांस"
    }
  },
  "region": {
    "id": "region_6b5jrtzvrz",
    "isoCode": "FR-IDF",
    "name": "Île-de-France",
    "names": {
      "en": "Île-de-France",
      "de": "Île-de-France",
      "es": "Isla de Francia",
      "fr": "Île-de-France",
      "it": "Île-de-France",
      "nl": "Île-de-France",
      "pt": "Ilha de França",
      "ru": "Иль-де-Франс",
      "ja": "イル・ド・フランス",
      "ko": "일드프랑스",
      "zh": "法兰西岛",
      "ar": "إيل دو فرانس",
      "hi": "इल-दा-फ़्रान्स"
    }
  },
  "coordinates": {
    "latitude": 48.85341,
    "longitude": 2.3488
  },
  "population": 2138551,
  "timeZone": "Europe/Paris",
  "isCapital": true,
  "disputed": false,
  "names": {
    "en": "Paris",
    "de": "Paris",
    "es": "París",
    "fr": "Paris",
    "it": "Parigi",
    "nl": "Parijs",
    "pt": "Paris",
    "ru": "Париж",
    "ja": "パリ",
    "ko": "파리",
    "zh": "巴黎",
    "ar": "باريس",
    "hi": "पैरिस"
  }
}
```

#### Worldviews

The `worldview` parameter sets the country of cities in disputed areas. The default is `us`.

The worldview changes these values:

- `country` of each city
- the `country` filter
- the [list of countries](/docs#listCountries)
- the country after a comma in search
- the country that reverse lookup prefers

If the worldview changes the country of a city, `region` is `null`. `disputed` does not change.

Country codes and names are not a political position.

| Value | Country |
| --- | --- |
| `ar` | Argentina |
| `cn` | China |
| `in` | India |
| `jp` | Japan |
| `ma` | Morocco |
| `rs` | Serbia |
| `ru` | Russia |
| `tr` | Türkiye |
| `us` | United States (default) |

#### Autocomplete

`GET /v1/cities/autocomplete`

Cities that start with the text. This route needs a key.

Parameters:

- `q` (string, query, required): The text to match, up to 100 characters.
- `limit` (integer, query): The number of cities to return. From 1 to 10. Default: 5.
- `country` (string, query): Up to 10 ISO 3166-1 alpha-2 codes, separated by commas.
- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

This route returns a list of [city objects](/docs#city-object) with this extra field:

- `match` (string): The type of match between the text and the city: exact, alias, prefix, or fuzzy.

[Errors](/docs#errors): 400, 401, 403, 429

Request:

```bash
curl "https://api.onde.earth/v1/cities/autocomplete?q=kyoto" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": [
    {
      "id": "city_39sy1dm2dj",
      "name": "Kyoto",
      "localName": "京都市",
      "type": "city",
      "parentCityId": null,
      "country": {
        "code": "JP",
        "name": "Japan"
      },
      "region": {
        "id": "region_yjwfrtj0fe",
        "isoCode": "JP-26",
        "name": "Kyoto"
      },
      "coordinates": {
        "latitude": 35.02107,
        "longitude": 135.75385
      },
      "population": 1463723,
      "timeZone": "Asia/Tokyo",
      "isCapital": false,
      "disputed": false,
      "match": "exact"
    }
  ],
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

#### Search

`GET /v1/cities/search`

Cities that match a full name. This route needs a key.

Parameters:

- `q` (string, query, required): The text to match, up to 100 characters.
- `limit` (integer, query): The number of cities to return. From 1 to 10. Default: 5.
- `country` (string, query): Up to 10 ISO 3166-1 alpha-2 codes, separated by commas.
- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us
- `region` (string, query): A region ID, such as region_yjwfrtj0fe.

This route returns a list of [city objects](/docs#city-object) with this extra field:

- `match` (string): The type of match between the text and the city: exact, alias, prefix, or fuzzy.

[Errors](/docs#errors): 400, 401, 403, 429

Request:

```bash
curl "https://api.onde.earth/v1/cities/search?q=kyoto" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": [
    {
      "id": "city_39sy1dm2dj",
      "name": "Kyoto",
      "localName": "京都市",
      "type": "city",
      "parentCityId": null,
      "country": {
        "code": "JP",
        "name": "Japan"
      },
      "region": {
        "id": "region_yjwfrtj0fe",
        "isoCode": "JP-26",
        "name": "Kyoto"
      },
      "coordinates": {
        "latitude": 35.02107,
        "longitude": 135.75385
      },
      "population": 1463723,
      "timeZone": "Asia/Tokyo",
      "isCapital": false,
      "disputed": false,
      "match": "exact"
    }
  ],
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

#### Reverse lookup

`GET /v1/cities/reverse`

The city at a position, or null. This route needs a key.

Reverse lookup prefers a large city to a small neighbor city. Reverse lookup also prefers a capital or the seat of a region within 6 km of the position. If no city is near the position, `data` is `null`. Near a border, the country can be wrong. `timeZone` is the time zone of the city. This time zone can differ from the time zone at the position.

Parameters:

- `latitude` (number, query, required): The latitude of the position.
- `longitude` (number, query, required): The longitude of the position.
- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

This route returns a [city object](/docs#city-object) with this extra field:

- `distanceKm` (number): The distance from the position in kilometers.

[Errors](/docs#errors): 400, 401, 403, 429

Request:

```bash
curl "https://api.onde.earth/v1/cities/reverse?latitude=35.02&longitude=135.75" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": {
    "id": "city_39sy1dm2dj",
    "name": "Kyoto",
    "localName": "京都市",
    "type": "city",
    "parentCityId": null,
    "country": {
      "code": "JP",
      "name": "Japan"
    },
    "region": {
      "id": "region_yjwfrtj0fe",
      "isoCode": "JP-26",
      "name": "Kyoto"
    },
    "coordinates": {
      "latitude": 35.02107,
      "longitude": 135.75385
    },
    "population": 1463723,
    "timeZone": "Asia/Tokyo",
    "isCapital": false,
    "disputed": false,
    "distanceKm": 0.37023042894617736
  },
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

#### Batch reverse lookup

`POST /v1/cities/reverse/batch`

The city at each position, or null, in the request order. This route needs a key.

`data` has the [reverse lookup](/docs#reverseCity) result for each position, at the index of the position. If no city is near a position, the result is `null`. If one position is not valid, the request fails and returns no results.

Parameters:

- `positions` (array, body, required): The positions. The results have the same order. From 1 to 100 items.
  - `latitude` (number, required): The latitude of the position. From -90 to 90.
  - `longitude` (number, required): The longitude of the position. From -180 to 180.
- `language` (string, body): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, body): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

This route returns a list of [city objects](/docs#city-object) with this extra field:

- `distanceKm` (number): The distance from the position in kilometers.

[Errors](/docs#errors): 400, 401, 403, 429

Request:

```bash
curl "https://api.onde.earth/v1/cities/reverse/batch" \
  -H "Authorization: Bearer $ONDE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"positions":[{"latitude":35.02,"longitude":135.75}]}'
```

Response 200:

```json
{
  "data": [
    {
      "id": "city_39sy1dm2dj",
      "name": "Kyoto",
      "localName": "京都市",
      "type": "city",
      "parentCityId": null,
      "country": {
        "code": "JP",
        "name": "Japan"
      },
      "region": {
        "id": "region_yjwfrtj0fe",
        "isoCode": "JP-26",
        "name": "Kyoto"
      },
      "coordinates": {
        "latitude": 35.02107,
        "longitude": 135.75385
      },
      "population": 1463723,
      "timeZone": "Asia/Tokyo",
      "isCapital": false,
      "disputed": false,
      "distanceKm": 0.37023042894617736
    }
  ],
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

#### Get a city

`GET /v1/cities/{id}`

The city with the ID. This route needs a key.

If the ID is retired and has a replacement, the API returns the replacement city, and `meta.retiredId` is the requested ID. Otherwise, `meta.retiredId` is `null`.

Parameters:

- `id` (string, path, required): The city ID.
- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

This route returns a [full city object](/docs#city-object), with all fields.

[Errors](/docs#errors): 400, 401, 403, 404, 410, 429

Request:

```bash
curl "https://api.onde.earth/v1/cities/city_f56eq77e1f" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": {
    "id": "city_f56eq77e1f",
    "name": "Paris",
    "localName": "Paris",
    "type": "city",
    "parentCityId": null,
    "country": {
      "code": "FR",
      "name": "France",
      "names": {
        "en": "France",
        "de": "Frankreich",
        "es": "Francia",
        "fr": "France",
        "it": "Francia",
        "nl": "Frankrijk",
        "pt": "França",
        "ru": "Франция",
        "ja": "フランス",
        "ko": "프랑스",
        "zh": "法国",
        "ar": "فرنسا",
        "hi": "फ़्रांस"
      }
    },
    "region": {
      "id": "region_6b5jrtzvrz",
      "isoCode": "FR-IDF",
      "name": "Île-de-France",
      "names": {
        "en": "Île-de-France",
        "de": "Île-de-France",
        "es": "Isla de Francia",
        "fr": "Île-de-France",
        "it": "Île-de-France",
        "nl": "Île-de-France",
        "pt": "Ilha de França",
        "ru": "Иль-де-Франс",
        "ja": "イル・ド・フランス",
        "ko": "일드프랑스",
        "zh": "法兰西岛",
        "ar": "إيل دو فرانس",
        "hi": "इल-दा-फ़्रान्स"
      }
    },
    "coordinates": {
      "latitude": 48.85341,
      "longitude": 2.3488
    },
    "population": 2138551,
    "timeZone": "Europe/Paris",
    "isCapital": true,
    "disputed": false,
    "names": {
      "en": "Paris",
      "de": "Paris",
      "es": "París",
      "fr": "Paris",
      "it": "Parigi",
      "nl": "Parijs",
      "pt": "Paris",
      "ru": "Париж",
      "ja": "パリ",
      "ko": "파리",
      "zh": "巴黎",
      "ar": "باريس",
      "hi": "पैरिस"
    }
  },
  "meta": {
    "dataset": "2026-11-17",
    "retiredId": null
  }
}
```

### Countries

These routes return the countries and the regions of a country. A region is a first-order division of a country. Use a region ID as the value of the `region` parameter of search.

- List countries: `GET /v1/countries`
- List regions: `GET /v1/countries/{code}/regions`

#### List countries

`GET /v1/countries`

All countries. This route needs a key.

Parameters:

- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

Returns:

- `code` (string): The ISO 3166-1 alpha-2 code.
- `name` (string): The name in the requested language.
- `continentCode` (string): The continent code.

[Errors](/docs#errors): 400, 401, 403, 429

Request:

```bash
curl "https://api.onde.earth/v1/countries" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": [
    {
      "code": "AD",
      "name": "Andorra",
      "continentCode": "EU"
    }
  ],
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

#### List regions

`GET /v1/countries/{code}/regions`

The regions of a country. This route needs a key.

Parameters:

- `code` (string, path, required): The ISO 3166-1 alpha-2 country code.
- `language` (string, query): The language of the names. Default: en. en, de, es, fr, it, nl, pt, ru, ja, ko, zh, ar, hi
- `worldview` (string, query): The worldview, which sets the country of a city in a disputed area. Default: us. ar, cn, in, jp, ma, rs, ru, tr, us

Returns:

- `id` (string): The value for the `region` parameter of search.
- `isoCode` (string or null): The ISO 3166-2 code, or null.
- `name` (string): The name in the requested language.

[Errors](/docs#errors): 400, 401, 403, 404, 429

Request:

```bash
curl "https://api.onde.earth/v1/countries/FR/regions" \
  -H "Authorization: Bearer $ONDE_KEY"
```

Response 200:

```json
{
  "data": [
    {
      "id": "region_84pj8sf7v2",
      "isoCode": "FR-ARA",
      "name": "Auvergne-Rhône-Alpes"
    }
  ],
  "meta": {
    "dataset": "2026-11-17"
  }
}
```

### Meta

This route returns the dataset version that the API serves.

- Get dataset details: `GET /v1/meta`

#### Get dataset details

`GET /v1/meta`

The dataset version. This route needs no key.

Returns:

- `dataset` (string): The dataset version.

Request:

```bash
curl "https://api.onde.earth/v1/meta"
```

Response 200:

```json
{
  "data": {
    "dataset": "2026-11-17"
  },
  "meta": {
    "dataset": "2026-11-17"
  }
}
```
