# Boundaries API — GeoSearch

> The GeoSearch Boundaries API: 1 endpoint with parameters, quota costs, response bodies and code samples in six languages.

Base URL: https://geosearch.dev · Auth: X-API-Key header on every request.
HTML version: https://geosearch.dev/docs/api/boundaries

Boundary polygons as GeoJSON. Charges 5 quota units per served polygon; region boundaries require a paid plan, country boundaries do not.

## Fetch an area's boundary polygon as GeoJSON

`GET /v1/boundaries/{geoname_id}` · Quota cost: 5 units

Returns 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 `area_not_an_area`, not a 400. |
| 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. `0` is accepted and is a no-op: the response reports `simplify: null`, because no tolerance was actually applied. A negative, non-finite or out-of-range value is a 422, never a 500. SUPPLY IT AT MOST ONCE. `?simplify=0.01&simplify=0.5` is a 422 rather than a request served with one of the two values silently dropped: two tolerances are two conflicting instructions, and the server does not guess which was meant. |
| lang | string | optional | ISO 639-1 language code for localized names (e.g., de, fr, ja) |

### Code samples

#### curl

```bash
curl -H "X-API-Key: YOUR_KEY" \
  "https://geosearch.dev/v1/boundaries/6252001?simplify=0.01"
```

#### Go

```go
// go get github.com/geosearch-dev/geosearch-go
client := geosearch.NewAPIClient(geosearch.NewConfiguration())
ctx := context.WithValue(context.Background(), geosearch.ContextAPIKeys,
    map[string]geosearch.APIKey{"apiKeyAuth": {Key: "YOUR_KEY"}})
boundary, _, err := client.BoundariesAPI.GetBoundary(ctx, 6252001).
    Simplify(0.01).Execute()
```

#### Python

```python
# pip install git+https://github.com/geosearch-dev/geosearch-python.git
import geosearch

cfg = geosearch.Configuration(api_key={"apiKeyAuth": "YOUR_KEY"})
with geosearch.ApiClient(cfg) as client:
    boundary = geosearch.BoundariesApi(client).get_boundary(6252001, simplify=0.01).data
```

#### TypeScript

```typescript
// npm install github:geosearch-dev/geosearch-typescript
import { Configuration, BoundariesApi } from "@geosearch/client";

const api = new BoundariesApi(new Configuration({ apiKey: "YOUR_KEY" }));
const boundary = (await api.getBoundary({ geonameId: 6252001, simplify: 0.01 })).data;
```

#### Ruby

```ruby
# gem 'geosearch', git: 'https://github.com/geosearch-dev/geosearch-ruby.git'
require "geosearch"

GeoSearch.configure { |c| c.api_key["X-API-Key"] = "YOUR_KEY" }
boundary = GeoSearch::BoundariesApi.new.get_boundary(6252001, simplify: 0.01).data
```

#### PHP

```php
// composer config repositories.geosearch vcs https://github.com/geosearch-dev/geosearch-php.git
// composer require geosearch-dev/geosearch-php
$cfg = GeoSearch\Configuration::getDefaultConfiguration()
    ->setApiKey("X-API-Key", "YOUR_KEY");
$boundary = (new GeoSearch\Api\BoundariesApi(null, $cfg))->getBoundary(6252001, 0.01)->getData();
```

### Response

```json
{
  "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`. |

