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/regionsBoundaries API
Boundary polygons as GeoJSON. Charges 5 quota units per served polygon; region boundaries require a paid plan, country boundaries do not.
Authentication: X-API-Key header on every request. Get a key.
Fetch an area's boundary polygon as GeoJSON
GET/v1/boundaries/{geoname_id}
Quota cost: 5 units Try itReturns the boundary polygon for one country or region as a bare GeoJSON geometry.
COSTS 5 QUOTA UNITS
This endpoint charges five units against the monthly quota, not one. The premium is priced on PAYLOAD rather than on query time: country GeoJSON averages 62.6 KB and reaches 1.9 MB, and region GeoJSON reaches 2.8 MB — two to three orders of magnitude above an ordinary list response.
?simplify= does NOT reduce the cost. It trades server CPU for client
bytes (simplification measures 209–248 ms against 10.3 ms for the plain
fetch), so the route is expensive to serve either way.
REJECTIONS COST ONE UNIT, NOT FIVE. A 400, a 422, either 404 and the 403 below all charge the standard single unit, so probing which ids are fetchable — and bouncing off the paywall — is not billed at the premium rate. Only a request that actually reaches the polygon fetch is charged five, including one that reaches it and then times out.
Plan requirements
REGION boundaries require a paid plan. COUNTRY boundaries are available
on every plan, including Free. A Free key requesting a region boundary
receives a 403 tier_upgrade_required carrying an upgrade link — a
visible refusal, not a silent omission.
This is the same split the geometry field follows on the country and
region endpoints, with one deliberate difference: there the field is
simply absent from a 200, while here the refusal is explicit and tells
you what to do about it.
Response shape
data.geometry is a BARE GeoJSON geometry — the {"type": ..., "coordinates": ...} object — and NOT a GeoJSON Feature. There is no
properties wrapper; the three sibling fields carry that information.
This matches the geometry /v1/countries/{code} and /v1/regions/{id}
already return.
type is Polygon OR MultiPolygon. Do not pin it: applying
?simplify= can collapse a MultiPolygon into a Polygon for areas whose
smaller parts disappear at the requested tolerance.
?fields= IS NOT SUPPORTED on this endpoint and is ignored if sent.
Field selection here works by serialising the whole object and then
dropping keys, so ?fields=name on a 1.9 MB polygon would build the
polygon in full and discard it — strictly more expensive than not
sending the parameter. Callers who want only the name should use
/v1/countries/{code} or /v1/regions/{id}.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| geoname_id | integer | required | GeoNames id of a country or a region. A city id — or any id that does not name an area — is a 404 |
| simplify | number | optional | Douglas-Peucker tolerance in EPSG:4326 DEGREES, applied before the polygon is serialised. Omit it for full precision. DEGREES, NOT METRES. The upper bound of 10 is roughly 1,100 km, chosen to make the unit obviously wrong to anyone who typed a value in metres. Simplification stops changing the shape above about 1 degree, so values beyond that buy nothing.
A negative, non-finite or out-of-range value is a 422, never a 500. SUPPLY IT AT MOST ONCE. |
| lang | string | optional | ISO 639-1 language code for localized names (e.g., de, fr, ja) |
Code samples
Response
{
"data": {
"geoname_id": 6252001,
"name": "United States",
"type": "country",
"geometry": {
"type": "MultiPolygon",
"coordinates": [
[
[
[
-124.7,
48.4
],
[
-124.6,
48.4
],
[
-124.6,
48.3
],
[
-124.7,
48.4
]
]
]
]
},
"simplify": null
}
}
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_request | The path segment is not an integer. A malformed id is a 400 rather than a 404, so a typo is distinguishable from a coverage gap. |
| 401 | authentication_required | No API key was supplied |
| 401 | authentication_failed | The supplied API key is not valid |
| 403 | tier_upgrade_required | The authenticated key's plan does not include the requested feature. NOT RETRYABLE, AND THIS MATTERS FOR CLIENT CODE. Generated SDKs and hand-written clients commonly retry 429 with backoff. This is a 403 and must not be routed into that path: no amount of waiting changes the answer, because nothing is exhausted and no window resets. The only resolution is to raise the plan at `error.upgrade.upgrade_url`. It carries no `Retry-After` and no `X-RateLimit-*` semantics of its own, which is the machine-readable form of the same statement. |
| 404 | area_not_an_area | The id is not an area |
| 404 | area_no_boundary | No boundary polygon is held for that area |
| 422 | validation_error | `simplify` was malformed. `error.details` names the field and what was wrong with it. A 422 HERE, WHERE `/v1/resolve` USES 400 FOR ITS COORDINATES. That is deliberate: this is a shape failure decidable from the request text alone and it has a field name to report, which is this API's 422 convention. The two endpoints follow two conventions; see the 400 on `/v1/resolve`. |
| 429 | rate_limit_exceeded | Per-second throttle exceeded |
| 429 | quota_exceeded | Monthly quota exhausted |
| 503 | area_query_timeout | Building the boundary polygon 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 polygon is large enough that assembling and serialising it ran out of its time budget. It is worth retrying. THE EFFECTIVE REMEDY ON THIS ROUTE IS `simplify`, not `bbox` — this operation has no `bbox` and no `within`. A larger tolerance means fewer vertices to generalise, serialise and transmit, so a request that times out at the full resolution frequently succeeds at `?simplify=0.01`. |
400 bad_request
{
"error": {
"code": "bad_request",
"message": "invalid boundary geoname_id",
"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"
}
}
403 tier_upgrade_required
{
"error": {
"code": "tier_upgrade_required",
"message": "Boundary geometry is not included on your current plan. Upgrade to fetch boundary polygons.",
"request_id": "req_abc123",
"upgrade": {
"upgrade_url": "https://geosearch.dev/dashboard#billing"
}
}
}
404 area_not_an_area
{
"error": {
"code": "area_not_an_area",
"message": "The path segment must be the GeoNames id of a country or a region. The id supplied names something else — a city, for instance — or names nothing at all.",
"request_id": "req_abc123"
}
}
404 area_no_boundary
{
"error": {
"code": "area_no_boundary",
"message": "That area exists, but we hold no boundary polygon for it yet. Coverage is strongest for countries and for first- and second-level regions; see the documentation for the current coverage map.",
"request_id": "req_abc123"
}
}
422 validation_error
{
"error": {
"code": "validation_error",
"message": "Validation failed",
"details": [
{
"field": "simplify",
"issue": "must not be negative; a tolerance is a distance, and PostGIS refuses a negative one"
}
],
"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": "Building that boundary polygon took too long to complete. This is usually temporary — please retry, or ask for a coarser polygon with a larger ?simplify= tolerance.",
"request_id": "req_abc123"
}
}