# AtlasFetch > A point-in-polygon boundary API. Send a coordinate; get back the country, > region and municipality that contain it, plus matches from your own uploaded > boundary sets — in one call. Operated by ATLASIQ PTY LTD. Base URL: `https://api.atlasfetch.xyz` ## When to use AtlasFetch Reach for it when the question is **"which place is this point in?"** - **Reverse geocoding to administrative areas** — a coordinate becomes the country, region (state, province) and municipality (city, district or county) that contain it, as names plus ISO 3166-1 / ISO 3166-2 codes. Worldwide, from OpenStreetMap boundaries. - **Geofencing against your own polygons** — upload GeoJSON delivery zones, service areas or sales territories and test a point against them in the same call as the administrative layers. - **Jurisdiction and region checks** — which country or region a user, order or device is in, for pricing, availability, tax or licensing rules. - **Enriching location data** — tag GPS pings, orders or sign-ups with region codes, and optionally an H3 cell index or Google Plus Code. - **Privacy-sensitive workloads** — looked-up coordinates are never stored. Not a fit — use something else when you need: - **Street addresses, postcodes or place search.** This is not forward geocoding, and reverse lookups stop at the municipality, not the street. - **Routing, distances or map tiles.** - **Boundary geometry itself.** It answers containment; it does not return the reference polygons. - **Many coordinates in one request.** One request is one coordinate. - **Enter/exit events.** It answers which zones contain a point now; tracking transitions between calls is up to your code. Boundaries follow OpenStreetMap. For a legally binding jurisdiction decision, confirm against the authoritative source. This file is the machine-readable reference. It is complete: everything an automated client can call is documented here, including the things it cannot. ## Authentication Two mechanisms, and the difference decides what an agent can do. - **API key** — `Authorization: Bearer `. A 64-character hex string, no prefix. This is what a program uses. - **Session cookie** — issued by an interactive Google OAuth flow in a browser. An automated client cannot obtain one. A key without a session is enough for lookups and for managing boundary sets. It is **not** enough to mint keys, read usage, or manage billing. ### Getting a key - **For trying it out**: `GET /demo/key` returns a shared public key, no signup. It belongs to one communal account whose quota, rate limit and single boundary set are pooled across every anonymous user. Fine for evaluation, wrong for anything real. - **For real use**: sign in at https://atlasfetch.xyz/dashboard and create one. Keys are stored hashed and shown exactly once. ## What an automated client can and cannot do Can, with an API key: - `GET /location/lookup` — the lookup itself - `GET|POST /boundaries/sets`, `PATCH|DELETE /boundaries/sets/:name` - `POST /boundaries`, `GET /boundaries/view`, `DELETE /boundaries/:id` - every public endpoint below Cannot, because these require a browser session: - `GET|POST|PATCH|DELETE /keys` — key management - `GET /me/quota`, `GET /me/usage`, `GET /me/traffic` — usage reporting and request-rate graphs - `GET /payments/subscription`, `POST /payments/cancel` — billing - `GET /auth/me`, `POST /auth/logout` **A known consequence, stated plainly:** a set is only queryable when it is `available` **and** granted to the specific key. Grants are addressed by API key **id**, and the only endpoint that lists key ids (`GET /keys`) needs a session. So a client holding only a raw API key can create a set and upload boundaries into it, but **cannot grant the set to itself** — that last step has to happen in the dashboard. Plan for that before scripting an upload. ## The lookup ``` GET https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276 Authorization: Bearer ``` | Parameter | Required | Default | Meaning | | --- | --- | --- | --- | | `lat` | yes | — | Latitude, -90 to 90 | | `lng` | yes | — | Longitude, -180 to 180 | | `base` | no | `country,region,municipal` | Comma list of reference layers to resolve | | `set` | no | none | Comma list of your own boundary sets to match against | | `encode` | no | none | Comma list of `h3`, `pluscode` — see Grid codes | | `h3res` | no | `9` | H3 resolution 0-15. r9 is ~400 m across | | `pluslen` | no | `10` | Plus Code length: 2, 4, 6, 8, 10, 11, 12, 13, 14, 15. **9 is not a code length.** Length 10 is ~14 m | Response `200`: ```json { "base": { "country": { "code": "GB", "name": "United Kingdom" }, "region": { "code": "GB-ENG", "name": "England" }, "municipal": { "code": "GB-WSM", "name": "City of Westminster" } }, "sets": { "delivery_zones": [ { "name": "Zone A", "properties": { "category": "express" } } ] }, "errors": [] } ``` Rules a client can rely on: - **`errors[]` is always present**, empty or not. Read it unconditionally. - **A layer that matched nothing is `null`**, not absent, for every layer named in `base`. - **`encoded` is absent entirely** unless `encode` was requested. - **Every response carries `X-Lookups-Remaining`.** - **Boundaries are identified by codes, never OSM ids.** Country is ISO 3166-1 (`GB`), region is ISO 3166-2 (`GB-ENG`), municipal is the official ISO 3166-2 code where OSM carries one, otherwise a generated `B--`. - **Generated `B-` codes are not yet stable across a full reseed.** Treat them as display labels, not durable keys. ISO codes are stable. - **`municipal` is the finest unit available, not a consistent kind of thing.** Los Angeles returns a city; rural Kansas returns Barton *County*; Madagascar may return a district where the commune is unmapped. Where a country's fine tier is patchy a coarser complete tier answers instead, because for most uses a county beats `null`. Do not assume the value names a city. ### Billing unit **One request is one lookup**, regardless of how many layers, sets or grid codes it touches. Metering happens *before* matching, so a call that matches nothing still costs a lookup — the work was done either way. A request rejected with `400` or `429` is not billed. ### Partial failure A set you cannot query does not fail the request. The set is skipped, an entry is appended to `errors[]`, and the status is still `200`: ```json { "base": { "country": { "code": "GB", "name": "United Kingdom" } }, "sets": {}, "errors": [ { "type": "access", "message": "Set 'zones' is not granted to key 'prod'" } ] } ``` `errors[].type` is `access` (a set was skipped) or `usage` (quota exhausted, mirrored from a `402`). ## Grid codes The looked-up point can be returned as a Google Plus Code and/or an H3 cell index: ``` GET https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276&encode=h3,pluscode ``` ```json { "base": { "country": { "code": "GB", "name": "United Kingdom" } }, "sets": {}, "encoded": { "h3": "89195da49b7ffff", "pluscode": "9C3XGV4C+VX" }, "errors": [] } ``` Encoding is not metered and not plan-gated. ### Grid codes are output only **`pluscode=` and `h3=` are not accepted as a location.** Sending one returns `400`. This is deliberate, not a missing feature. A Plus Code and an H3 index name **areas, not points**. A length-4 Plus Code is a 111 km square; an H3 cell at resolution 4 is 52 km across. Accepting one as the location would mean picking a representative point inside it and returning whichever boundary contains that point as though it were the answer — one of several equally defensible answers, with no way for the caller to tell which situation they were in. If you have a cell and want a lookup, decode it yourself and decide what its centre means for your use case. ### Cell sizes H3 resolution to approximate width: 0 ~2563 km, 2 ~365 km, 4 ~52 km, 6 ~7.4 km, 8 ~1.1 km, 9 ~402 m, 10 ~152 m, 11 ~57 m, 12 ~22 m, 15 ~1 m. Plus Code length to approximate width at the equator: 2 ~2226 km, 4 ~111 km, 6 ~5.6 km, 8 ~278 m, 10 ~14 m, 11 ~3 m, 12 ~56 cm. ## Custom boundary sets Your own polygons, grouped into named sets, matched in the same call as the reference layers. Accepts an API key or a session. | Method | Path | Purpose | | --- | --- | --- | | `GET` | `/boundaries/sets` | List sets with counts and granted key ids | | `POST` | `/boundaries/sets` | Create a set. Body `{"name":"zones"}`. Returns `201` | | `PATCH` | `/boundaries/sets/:name` | Body `{"available":true,"grant":[""],"revoke":[]}` | | `DELETE` | `/boundaries/sets/:name` | Delete the set and everything in it | | `POST` | `/boundaries` | Add a boundary. Body `{"set","name","geometry","properties"}`. Returns `201` | | `GET` | `/boundaries/view?set=zones` | Your own boundaries, with geometry | | `DELETE` | `/boundaries/:id` | Remove one boundary | `geometry` is GeoJSON. `properties` is a flat object. **A new set is created with `available: false` and no grants**, so it will not match anything until you both switch it on and grant it to a key. See the note under "What an automated client can and cannot do" — the grant step needs a key id you can only get from a session. ## Public endpoints No authentication, no metering. | Method | Path | Purpose | | --- | --- | --- | | `GET` | `/health` | Liveness. `{"status":"ok"}` | | `GET` | `/demo/key` | The shared public demo key | | `GET` | `/payments/plans` | Plan catalogue with prices and caps | | `GET` | `/version` | Which API build is serving. `{"version":"v21","revision":"atlasfetch-api-…"}` | | `GET` | `/openapi.json` | OpenAPI 3.1 description of everything callable with a key | **There is no bulk geometry endpoint.** `/explore/world` and `/explore/country/:code` previously served simplified country outlines and were removed on 2026-09-08: unauthenticated, uncached and expensive (a 243 KB payload taking 16.8 s per call), they were the one lever an anonymous caller could pull against the database. The API answers point-in-polygon questions; it does not hand out boundary geometry. ## Status codes | Code | Meaning | Retry? | | --- | --- | --- | | `200` | Success. Check `errors[]` for per-set problems | — | | `400` | Invalid parameters. `{"error":"..."}` explains what | No — fix the request | | `401` | Missing, malformed or revoked API key | No | | `402` | Monthly lookup allowance exhausted | Not until the period resets | | `403` | Your plan does not include this, or a cap was exceeded | No | | `404` | Not found | No | | `409` | Conflict, e.g. a duplicate set name | No | | `429` | Per-key rate limit. Honour `Retry-After` | Yes, after the delay | Rate limiting is per key, per second. Quota is per account, per month. ## Plans | | Public | Personal | Basic | Pro | | --- | --- | --- | --- | --- | | Price/month | free | free | GBP 9 | GBP 29 | | Lookups | 1,000 (shared) | 1,000 | 50,000 | 250,000 | | Requests/sec | 2 | 5 | 10 | 25 | | Sets | 1 | 1 | 5 | 25 | | Boundaries/set | 100 | 10 | 1,000 | 10,000 | | Points/boundary | 5 | 50 | 500 | 5,000 | | Properties/boundary | 3 (fixed keys) | 5 | 10 | 25 | | At the boundary cap | oldest evicted | rejected | rejected | rejected | Public is the single shared account behind the demo key. Caps are provisional and may change before general availability. ## Worked examples Resolve a coordinate: ``` curl -H "Authorization: Bearer $KEY" \ "https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276" ``` Country only, with both grid codes at coarse precision: ``` curl -H "Authorization: Bearer $KEY" \ "https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276&base=country&encode=h3,pluscode&h3res=4&pluslen=6" ``` Try it with no signup: ``` KEY=$(curl -s https://api.atlasfetch.xyz/demo/key | jq -r .key) curl -H "Authorization: Bearer $KEY" \ "https://api.atlasfetch.xyz/location/lookup?lat=-33.9249&lng=18.4241" ``` ## Traps worth knowing 1. **`encode` is one-way.** `h3=`/`pluscode=` as input is a `400`, on purpose. 2. **`pluslen=9` is invalid.** Plus Code lengths are 2, 4, 6, 8, then 10-15. 3. **URL-encode the `+` in a Plus Code** if you pass one anywhere: `%2B`. 4. **A new set matches nothing** until `available: true` *and* granted to a key. 5. **A skipped set is a `200`, not an error status.** Read `errors[]`. 6. **`B-` municipal codes are not stable across reseeds.** ISO codes are. 7. **A lookup that matches nothing still bills.** Metering precedes matching. 8. **`/location/info` is a deprecated alias** of `/location/lookup`, identical in behaviour, billed under its own name. Use `/location/lookup` in new work. ## Data and licensing Reference boundaries derive from OpenStreetMap, licensed under the Open Database License (ODbL). Attribution and share-alike obligations pass through to you — see https://atlasfetch.xyz/attribution. Your own uploaded boundaries are private to your account and are never returned to anyone else. ## Privacy Coordinates you look up are **not stored**. Usage metering records the endpoint, the key and a timestamp — never the point. ## Links - Terms: https://atlasfetch.xyz/terms - Privacy: https://atlasfetch.xyz/privacy - Attribution: https://atlasfetch.xyz/attribution - OpenAPI 3.1 spec: https://api.atlasfetch.xyz/openapi.json - Interactive playground: https://atlasfetch.xyz/playground - Human-readable docs: https://atlasfetch.xyz/docs