Documentation navigation
Guides
Getting started Boundary searchAPI reference
Overview Health GET/v1/status Countries GET/v1/countries GET/v1/countries/{code} GET/v1/countries/{code}/regions GET/v1/countries/{code}/neighbors Regions GET/v1/regions GET/v1/regions/{id} GET/v1/regions/{id}/cities GET/v1/regions/{id}/children Cities GET/v1/cities GET/v1/cities/{id} GET/v1/cities/{id}/hierarchy GET/v1/cities/nearby Postal Codes GET/v1/postal-codes GET/v1/postal-codes/nearest Timezones GET/v1/timezones GET/v1/timezones/{tzId} IP Geolocation GET/v1/ip/{address} GET/v1/ip/me Search GET/v1/autocomplete GET/v1/reverse GET/v1/resolve GET/v1/search Boundaries GET/v1/boundaries/{geoname_id} Batch POST/v1/batch/cities POST/v1/batch/countries POST/v1/batch/regionsCities API
City data with geographic coordinates
Authentication: X-API-Key header on every request. Get a key.
Returns a paginated list of cities with optional filtering by country, admin code, name, population, timezone, and elevation.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| lang | string | optional | ISO 639-1 language code for localized names (e.g., de, fr, ja) |
| country | string | optional | Filter by ISO alpha-2 country codes (comma-separated) |
| admin1 | string | optional | Filter by admin1 code (state/province) |
| name | string | optional | Filter by city name (trigram fuzzy search) |
| population_min | integer | optional | Minimum population filter |
| population_max | integer | optional | Maximum population filter |
| timezone | string | optional | Filter by IANA timezone ID |
| min_elevation | integer | optional | Minimum elevation in meters |
| max_elevation | integer | optional | Maximum elevation in meters |
| within | integer | optional | Return only results whose coordinates fall geometrically inside the boundary of the given area, identified by its GeoNames id. Countries and administrative regions are valid areas; a city id is not. Accepts exactly one id. A COMMA-SEPARATED LIST IS REJECTED with a 422 naming the limit — a deliberate departure from the comma-separated convention COST: a request using this parameter consumes 2 quota units instead of 1, on every plan including Free. Combining it with This asks a GEOMETRIC question and can therefore return a different set of cities than Two failures are reported with distinct 400 codes so a typo is distinguishable from a coverage gap: |
| bbox | string | optional | Return only results inside the bounding box, given as four comma-separated numbers in the order Longitudes must be within [-180, 180] and latitudes within [-90, 90]. A box where WEST IS GREATER THAN EAST wraps the antimeridian and is fully supported: Charged at the standard request cost of 1 unit. Adding it to a |
| cursor | string | optional | Pagination cursor from a previous response |
| limit | integer | optional | Number of results per page (1-100, default 25) |
| fields | string | optional | Comma-separated list of fields to include in the response |
| sort | string | optional | Sort field. Allowed: name, population, elevation, id. |
Code samples
Response
{
"data": [
{
"id": 5391959,
"geoname_id": 5391959,
"name": "San Francisco",
"ascii_name": "San Francisco",
"country_code": "US",
"admin1_code": "CA",
"population": 873965,
"timezone": "America/Los_Angeles",
"latitude": 37.77493,
"longitude": -122.41942
}
],
"meta": {
"next_cursor": "eyJpZCI6NTM5MTk2MH0",
"has_next": true,
"has_prev": false,
"count": 25
}
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | area_not_an_area | The within id is not an area |
| 400 | area_no_boundary | No boundary polygon is held for that area |
| 400 | bad_request | A malformed cursor, sort or pagination value |
| 401 | authentication_required | No API key was supplied |
| 401 | authentication_failed | The supplied API key is not valid |
| 422 | validation_error | More than one area requested |
| 422 | validation_error | A bounding box whose south edge is north of its north edge |
| 429 | rate_limit_exceeded | Per-second throttle exceeded |
| 429 | quota_exceeded | Monthly quota exhausted |
| 503 | area_query_timeout | The containment query exceeded the statement timeout that bounds it. This is a 503 and NOT a 500, deliberately: the server is healthy and the request was valid — this one query against an unusually large or complex boundary simply ran out of its time budget. It is therefore worth retrying, and worth retrying with a narrower query. Adding `bbox` or further filters alongside `within` reduces the candidate set before the polygon test and is the most effective remedy. THIS RESPONSE BELONGS TO THE `within=` ROUTES ONLY. `GET /v1/resolve` and `GET /v1/boundaries/{geoname_id}` have their own 503 components (`ResolveQueryTimeout` and `BoundaryQueryTimeout`) because the remedy above is false on both: neither accepts `bbox` or `within`. |
400 area_not_an_area
{
"error": {
"code": "area_not_an_area",
"message": "The `within` parameter must be the GeoNames id of a country or a region, for example within=6252001 for the United States. The id supplied names something else — a city, for instance — or names nothing at all.",
"request_id": "req_abc123"
}
}
400 area_no_boundary
{
"error": {
"code": "area_no_boundary",
"message": "That area exists, but no boundary polygon is available for it yet, so containment cannot be computed. Boundary coverage is strongest for countries and first- and second-level regions; see the documentation for current coverage.",
"request_id": "req_abc123"
}
}
400 bad_request
{
"error": {
"code": "bad_request",
"message": "invalid cursor",
"request_id": "req_abc123"
}
}
401 authentication_required
{
"error": {
"code": "authentication_required",
"message": "API key required. Get one at /signup",
"request_id": "req_abc123"
}
}
401 authentication_failed
{
"error": {
"code": "authentication_failed",
"message": "Invalid API key",
"request_id": "req_abc123"
}
}
422 validation_error
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": [
{
"field": "within",
"issue": "accepts exactly one geoname_id; comma-separated lists are not supported"
}
],
"request_id": "req_abc123"
}
}
422 validation_error
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": [
{
"field": "bbox",
"issue": "south latitude must be less than north latitude"
}
],
"request_id": "req_abc123"
}
}
429 rate_limit_exceeded
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again later.",
"request_id": "req_abc123"
}
}
429 quota_exceeded
{
"error": {
"code": "quota_exceeded",
"message": "Monthly quota exhausted. You've used 2,000,000 of 2,000,000 requests for the current period, which resets on March 1, 2026. Upgrade for a higher limit.",
"request_id": "req_abc123",
"quota": {
"limit": 2000000,
"used": 2000000,
"resets_at": "2026-03-01T00:00:00Z",
"upgrade_url": "https://geosearch.dev/dashboard/billing"
}
}
}
503 area_query_timeout
{
"error": {
"code": "area_query_timeout",
"message": "The containment query for that area took too long to complete. This is usually temporary — please retry. If it persists, narrow the query by adding a bbox or additional filters.",
"request_id": "req_abc123"
}
}
Returns a single city by its numeric ID.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | City ID |
| lang | string | optional | ISO 639-1 language code for localized names (e.g., de, fr, ja) |
| fields | string | optional | Comma-separated list of fields to include in the response |
Code samples
Response
{
"data": {
"id": 5391959,
"geoname_id": 5391959,
"name": "San Francisco",
"ascii_name": "San Francisco",
"country_code": "US",
"admin1_code": "CA",
"admin2_code": "075",
"population": 873965,
"elevation": 16,
"timezone": "America/Los_Angeles",
"latitude": 37.77493,
"longitude": -122.41942,
"country": {
"iso_code": "US",
"name": "United States"
},
"region": {
"id": 5332921,
"name": "California",
"admin_code": "CA"
}
}
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | Invalid request parameters |
| 401 | authentication_required | No API key was supplied |
| 401 | authentication_failed | The supplied API key is not valid |
| 404 | not_found | Resource not found |
| 429 | rate_limit_exceeded | Per-second throttle exceeded |
| 429 | quota_exceeded | Monthly quota exhausted |
400 validation_error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "population_min",
"issue": "must be a valid integer"
}
],
"request_id": "req_abc123"
}
}
401 authentication_required
{
"error": {
"code": "authentication_required",
"message": "API key required. Get one at /signup",
"request_id": "req_abc123"
}
}
401 authentication_failed
{
"error": {
"code": "authentication_failed",
"message": "Invalid API key",
"request_id": "req_abc123"
}
}
404 not_found
{
"error": {
"code": "not_found",
"message": "The requested resource was not found",
"request_id": "req_abc123"
}
}
429 rate_limit_exceeded
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again later.",
"request_id": "req_abc123"
}
}
429 quota_exceeded
{
"error": {
"code": "quota_exceeded",
"message": "Monthly quota exhausted. You've used 2,000,000 of 2,000,000 requests for the current period, which resets on March 1, 2026. Upgrade for a higher limit.",
"request_id": "req_abc123",
"quota": {
"limit": 2000000,
"used": 2000000,
"resets_at": "2026-03-01T00:00:00Z",
"upgrade_url": "https://geosearch.dev/dashboard/billing"
}
}
}
Returns the full administrative hierarchy for a city, ordered from the city itself up through region, country, and continent.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| id | integer | required | City ID |
| lang | string | optional | ISO 639-1 language code for localized names (e.g., de, fr, ja) |
Code samples
Response
{
"data": [
{
"geoname_id": 5391959,
"name": "San Francisco",
"type": "city",
"depth": 0
},
{
"geoname_id": 5332921,
"name": "California",
"type": "region",
"depth": 1
},
{
"geoname_id": 6252001,
"name": "United States",
"type": "country",
"depth": 2
},
{
"geoname_id": 6255149,
"name": "North America",
"type": "continent",
"depth": 3
}
],
"meta": {
"count": 4
}
}
Errors
| Status | Code | When |
|---|---|---|
| 401 | authentication_required | No API key was supplied |
| 401 | authentication_failed | The supplied API key is not valid |
| 404 | not_found | Resource not found |
| 429 | rate_limit_exceeded | Per-second throttle exceeded |
| 429 | quota_exceeded | Monthly quota exhausted |
401 authentication_required
{
"error": {
"code": "authentication_required",
"message": "API key required. Get one at /signup",
"request_id": "req_abc123"
}
}
401 authentication_failed
{
"error": {
"code": "authentication_failed",
"message": "Invalid API key",
"request_id": "req_abc123"
}
}
404 not_found
{
"error": {
"code": "not_found",
"message": "The requested resource was not found",
"request_id": "req_abc123"
}
}
429 rate_limit_exceeded
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again later.",
"request_id": "req_abc123"
}
}
429 quota_exceeded
{
"error": {
"code": "quota_exceeded",
"message": "Monthly quota exhausted. You've used 2,000,000 of 2,000,000 requests for the current period, which resets on March 1, 2026. Upgrade for a higher limit.",
"request_id": "req_abc123",
"quota": {
"limit": 2000000,
"used": 2000000,
"resets_at": "2026-03-01T00:00:00Z",
"upgrade_url": "https://geosearch.dev/dashboard/billing"
}
}
}
Returns cities near a given latitude/longitude within a specified radius. Results are ordered by distance. Uses PostGIS spatial index for fast lookups.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| lat | number | required | Latitude (-90 to 90) |
| lon | number | required | Longitude (-180 to 180) |
| radius | number | optional | Search radius in kilometers (default 50, max 200) |
| limit | integer | optional | Maximum results to return (1-250, default 10) |
| fields | string | optional | Comma-separated list of fields to include in the response |
Code samples
Response
{
"data": [
{
"id": 5391959,
"name": "San Francisco",
"country_code": "US",
"population": 873965,
"timezone": "America/Los_Angeles",
"latitude": 37.77493,
"longitude": -122.41942,
"distance_km": 0.5
}
],
"meta": {
"count": 1
}
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | validation_error | Invalid request parameters |
| 401 | authentication_required | No API key was supplied |
| 401 | authentication_failed | The supplied API key is not valid |
| 429 | rate_limit_exceeded | Per-second throttle exceeded |
| 429 | quota_exceeded | Monthly quota exhausted |
400 validation_error
{
"error": {
"code": "validation_error",
"message": "Invalid request parameters",
"details": [
{
"field": "population_min",
"issue": "must be a valid integer"
}
],
"request_id": "req_abc123"
}
}
401 authentication_required
{
"error": {
"code": "authentication_required",
"message": "API key required. Get one at /signup",
"request_id": "req_abc123"
}
}
401 authentication_failed
{
"error": {
"code": "authentication_failed",
"message": "Invalid API key",
"request_id": "req_abc123"
}
}
429 rate_limit_exceeded
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again later.",
"request_id": "req_abc123"
}
}
429 quota_exceeded
{
"error": {
"code": "quota_exceeded",
"message": "Monthly quota exhausted. You've used 2,000,000 of 2,000,000 requests for the current period, which resets on March 1, 2026. Upgrade for a higher limit.",
"request_id": "req_abc123",
"quota": {
"limit": 2000000,
"used": 2000000,
"resets_at": "2026-03-01T00:00:00Z",
"upgrade_url": "https://geosearch.dev/dashboard/billing"
}
}
}