Docs
API reference
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. Optionally, the nearest street and house number. It also routes between coordinates, and can route around the zones you upload; and it geocodes addresses to coordinates, in South Africa today.
Reading this as an AI agent?
Fetch the machine-readable reference instead. It is plain Markdown, complete, and covers what an automated client cannot do as well as what it can.
https://atlasfetch.xyz/llms.txtNew here? Start with a guide
Base URL
https://api.atlasfetch.xyz
Every path on this page is relative to it. Lookups go to https://api.atlasfetch.xyz/location/lookup — not to atlasfetch.xyz, which is the website.
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.
- The nearest street — the street a point is on, and the house number where one is mapped, with the distance to each. Worldwide, in 216 countries.
- Directions — a car, bike, foot or truck route between 2 and 25 coordinates, with turn-by-turn steps, routed around the zones you name and measured against the ones you watch. A route costs 10 lookups on the standard plans.
- Geocoding addresses for delivery — an address in, a point out, with a
precisionthat says whether it found the house, the street or only the suburb, so you know when to ask for a pin. South Africa today; one lookup per address, singly or in batches. - 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
- Geocoding outside South Africa, or place search. Addresses geocode in ZA only for now, and there is no search for businesses or landmarks by name.
- A guaranteed house number. OpenStreetMap maps them well in some countries and barely in others — in ZA, on about 13% of street names.
- Traffic-aware ETAs. Routes exist, but their durations are free-flow — speed limits and road classes, with no live or historical traffic. For an arrival time in traffic, use Google or Mapbox.
- Distance matrices, isochrones or map tiles. Many-to-many distances and reachability areas are planned, not built.
- 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.
Authentication
Two mechanisms, and the difference decides what a program can do.
- API key —
Authorization: Bearer <key>. A 64-character hex string, no prefix. This is what a program uses. - Session cookie — issued by an interactive Google sign-in in a browser. An automated client cannot obtain one.
A key is enough for lookups, routes, geocoding and managing boundary sets. It is not enough to mint keys, read usage, or manage billing.
Create a key on the dashboard, or call GET https://api.atlasfetch.xyz/demo/key for a shared public one — no signup, but its 1,000 lookups a month and its rate limit are pooled across every anonymous user. Use it to try things, not to ship.
You can show and copy a key from the dashboard whenever you need it. That also means anyone who reaches the dashboard can read it, so restrict each key to your own domains there — treat that as the control, not an option. Keys made before 12 October 2026 were revoked when keys became viewable; a revoked key’s 401 says so.
The lookup
GET https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276 Authorization: Bearer <key>
curl -H "Authorization: Bearer $ATLASFETCH_KEY" \ "https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276"
| Parameter | Default | Meaning |
|---|---|---|
| lat | required | Latitude, −90 to 90 |
| lng | required | Longitude, −180 to 180 |
| base | country, region, municipal | Comma list: country, region, municipal, street (opt-in) |
| set | none | Comma list of your own boundary sets |
| encode | none | Comma list: h3, pluscode |
| h3res | 9 | H3 resolution 0–15. r9 is ~400 m across |
| pluslen | 10 | Plus Code length: 2, 4, 6, 8, 10–15 |
| streetgeom | none | polyline6 or geojson: each street answer's line. Needs street in base |
{
"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": []
}Guarantees you can rely on
errors[]is always present, empty or not. Read it unconditionally.- A layer that matched nothing is
null, not absent. encodedis absent entirely unlessencodewas asked for.- Every response carries
X-Lookups-Remaining. - Boundaries are identified by codes, never OSM ids — ISO 3166-1 for country, ISO 3166-2 for region, and for municipal the official ISO code where OSM carries one, otherwise a generated
B-<country>-<n>. municipalis the finest unit available, not a consistent kind of thing. Los Angeles returns a city; rural Kansas returns Barton County. Where a country’s fine tier is patchy, a coarser complete tier answers instead — a county beatsnullfor most uses. Do not assume the value names a city.
B- codes are not yet stable across a full reseed. Treat them as display labels rather than durable keys. ISO codes are stable.Streets
Add street to base and the response gains a fourth layer: the nearest street, answered three times — within 5 m, within 20 m, and however far. Always three entries, in that order. It is not in the default base, so asking for it changes nothing for existing callers, and it costs no extra lookup.
GET https://api.atlasfetch.xyz/location/lookup?lat=-33.9242&lng=18.421&base=country,region,municipal,street
"street": [
{ "radiusMeters": 5, "streetNumber": null, "streetName": "Adderley Street", "postcode": null, "distanceMeters": 2.7 },
{ "radiusMeters": 20, "streetNumber": "25", "streetName": "Adderley Street", "postcode": "8001", "distanceMeters": 9.6 },
{ "radiusMeters": null, "streetNumber": "25", "streetName": "Adderley Street", "postcode": "8001", "distanceMeters": 9.6 }
]How each answer is picked
- A house number within the radius wins, even over a nearer street — it is the more precise answer, and it carries the address’s own street. For the unlimited answer the house must still be within 20 m; further than that it is rarely the property the point is on.
- Otherwise the nearest named street. A road known only by its route number answers with that:
N1. - Otherwise every field except
radiusMetersisnull. Unnamed roads are never an answer. distanceMetersis to the building (0when the point is inside it) or to the nearest point of the street, to 0.1 m.
What to expect
- Streets cover 216 countries.
street: nullmeans the point is outside all of them — at sea, in Antarctica, or in one of the two areas without an ISO country code (Western Sahara; Akrotiri and Dhekelia). An entry of nulls means covered, but nothing within that radius.GET https://api.atlasfetch.xyz/streets/coveragelists every country with its road and address counts. - House numbers depend on the country. Where OpenStreetMap has few addresses, most answers are a street name and a distance.
postcodeonly comes with a house number. streetNumberandpostcodeare strings: numbers look like44Aor12-14, and some postcodes start with 0.- No street geometry unless you ask. With
streetgeom=polyline6(orgeojson), each answer gainsgeometry: the named street’s line, trimmed to about 250 m either side of the point. A house-number answer gets its own street’s line; an answer naming nothing getsnull. Without it there is nogeometrykey at all. Still one lookup;streetgeomwithoutstreetis a400. - Streets come from OpenStreetMap, like the boundaries. OpenStreetMap ids and road attributes never leave the API.
One call, one lookup
A request is exactly 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.
A route is the exception: POST /route costs 10 lookups on the standard plans, whatever it asks for — steps, alternatives, avoid zones and exposure are included in that one charge. A geocoded address costs 1, found or not. Everything draws on the same monthly allowance.
X-Lookups-Remaining (and a geocoding batch’s remaining) is the truth for the caller: it includes any extra allowance the account has been given, and reflects a custom plan’s prices. Read it rather than working it out.
Partial failure is still a 200
A set you cannot query does not fail the request. It is skipped, an entry is appended to errors[], and the status stays 200. A set is only queryable when it is marked available and granted to the key making the call.
"errors": [
{ "type": "access", "message": "Set 'zones' is not granted to key 'prod'" }
]type is access (a set was skipped) or usage (quota exhausted, mirroring a 402).
Grid codes
The looked-up point can be returned as a Google Plus Code and/or an H3 cell index. Not metered, not plan-gated.
GET https://api.atlasfetch.xyz/location/lookup?lat=51.5072&lng=-0.1276&encode=h3,pluscode
"encoded": { "h3": "89195da49b7ffff", "pluscode": "9C3XGV4C+VX" }They are output only
pluscode= and h3= are not accepted as a location; sending one returns 400. That 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 you to tell which situation you were in.
If you have a cell and want a lookup, decode it yourself and decide what its centre means for your use case. That keeps the judgement where the context is.
Picking a precision
| H3 | Width | Plus Code | Width |
|---|---|---|---|
| r4 | ~52 km | 4 | ~111 km |
| r6 | ~7.4 km | 6 | ~5.6 km |
| r8 | ~1.1 km | 8 | ~278 m |
| r9 (default) | ~402 m | 10 (default) | ~14 m |
| r11 | ~57 m | 11 | ~3 m |
| r15 | ~1 m | 12 | ~56 cm |
Plus Code widths are at the equator. pluslen=9 is invalid — 9 is not a Plus Code length.
Custom boundary sets
Your own polygons, grouped into named sets, matched in the same call as the reference layers. These endpoints accept an API key or a session.
| Method | Path | Purpose |
|---|---|---|
| GET | /boundaries/sets | List sets with boundary counts and granted keys |
| POST | /boundaries/sets | Create a set |
| PATCH | /boundaries/sets/:name | Toggle available, grant/revoke keys |
| DELETE | /boundaries/sets/:name | Delete the set and its contents |
| POST | /boundaries | Add one GeoJSON Polygon |
| GET | /boundaries/view?set= | The set and its boundaries, with geometry |
| PATCH | /boundaries/:id | Replace one boundary's shape; body {"geometry": <Polygon>} only |
| DELETE | /boundaries/:id | Remove one boundary |
available: false with no grants, so it matches nothing until you switch it on and grant it to a key. Any key but the shared demo key can switch a set on and grant it, but grants are addressed by API key id, and nothing a key can call tells it its own id — so the first grant happens in the dashboard. After that the key’s id appears in GET /boundaries/sets and later grants can be scripted. A polygon may have at most 50 points on every plan, the closing one included. The shared demo key cannot change or delete a set.PATCH /boundaries/:id moves or reshapes a zone without losing it: the id, name, properties and set stay as they are. The new shape gets every check POST /boundaries applies, including the plan’s points-per-boundary cap (403). Any other field is a 400; a boundary on another account is a 404. Deleting and re-adding does the same job worse — a new id, and the properties sent again.
Directions
A route between coordinates, worldwide, over the same OpenStreetMap network the street layer uses. Coordinates travel in the body, never a URL, and are not stored.
POST https://api.atlasfetch.xyz/route
Authorization: Bearer <key>
Content-Type: application/json
{
"locations": [ { "lat": -33.9242, "lng": 18.4210 }, { "lat": -33.9068, "lng": 18.4201 } ],
"mode": "car",
"steps": true
}| Field | Default | Meaning |
|---|---|---|
| locations | required | 2 to 25 {lat,lng} in order: origin, stops, destination. The plan caps how many |
| mode | car | car, bike, foot, truck. truck is Pro only |
| steps | false | Turn-by-turn steps per leg |
| geometry | none | none, polyline6, geojson — the route line, off by default because it is long |
| language | en-US | Locale for the step instructions, e.g. en-GB, de-DE |
| alternatives | 0 | Up to the plan's cap. Exactly 2 locations only |
| avoid.sets | none | Names of your boundary sets to route around |
| exposure.sets | none | Names of your boundary sets to measure against |
| vehicle | truck defaults | Height, width, length, weight, axle load, hazmat. Only with truck |
Real output, Cape Town CBD to the V&A Waterfront, trimmed to two steps:
{
"route": {
"mode": "car",
"distanceMeters": 3513,
"durationSeconds": 306,
"legs": [
{
"distanceMeters": 3513,
"durationSeconds": 306,
"steps": [
{ "maneuver": "depart", "instruction": "Drive southwest on Adderley Street.", "streetNames": ["Adderley Street"], "distanceMeters": 118, "durationSeconds": 11 },
{ "maneuver": "turn-right", "instruction": "Turn right onto Buitengracht Street/M62.", "streetNames": ["Buitengracht Street", "M62"], "distanceMeters": 1116, "durationSeconds": 82 }
]
}
]
},
"locations": [
{ "lat": -33.924185, "lng": 18.420978, "snapDistanceMeters": 2.6 },
{ "lat": -33.907242, "lng": 18.419687, "snapDistanceMeters": 62.2 }
],
"warnings": [],
"errors": []
}- Durations are free-flow. Speed limits and road classes, with no live or historical traffic and no departure time. Do not show one as an ETA.
- Metres and seconds, everywhere.
steps,geometry,exposure,alternativesandavoidare absent unless asked for, the same rule asencodedon a lookup.locations[]holds the snapped points, andsnapDistanceMeterssays how far each moved to reach a road. 62 m above means the route ends on the nearest road, not at the pin.maneuveris a fixed vocabulary:depart,arrive,continue, the four turns with their slight and sharp variants,u-turn,ramp,exit,merge,fork,roundabout,ferry,other.
Trucks
truck is Pro only. A truck over 9 t, or with no weightTonnes, is capped at 80 km/h, as South African law caps goods vehicles over 9 t; a lighter truck keeps car speeds. Cape Town to Stellenbosch: a 30 t truck takes a shorter, slower road (44.6 km, 38 min), a 3.5 t truck matches the car (50.6 km, 35 min). The vehicle’s height, width, length, weight, axle load and hazmat are checked against OpenStreetMap’s limits on each road: Hout Bay to Noordhoek, a car goes over Chapman’s Peak (14.0 km, 2.7 m height limit) and a 3.5 m truck goes round by Constantia (32.4 km). Restrictions are only as complete as OpenStreetMap’s tags, so treat a truck route as a good start, not a legal clearance.
Your own zones, in a route
avoid.sets and exposure.sets take the names of boundary sets on your account — the same sets a lookup matches against — not shapes in the request. Avoid excludes every zone in those sets; exposure changes nothing and reports how many metres of the route ran inside each zone.
"route": {
"exposure": [
{ "set": "risk_watch", "zone": "Station hotspot", "properties": { "band": "high" }, "metersInside": 426.6 }
]
},
"avoid": {
"applied": [ { "set": "no_go", "zone": "Roadworks" } ],
"skipped": [ { "set": "no_go", "zone": "Customer estate", "reason": "contains_location", "locationIndex": 1 } ]
}avoid.skipped, and a warnings[] entry names it. An avoid set that is missing, switched off or not granted to the key is a hard 403, unlike a lookup, where an unusable set is skipped: routing around a zone you cannot read is not something to guess at. A bad exposure set is soft — it lands in errors[] and the route still answers. So does a route along more than 1,000 exposure zones, or one whose zones take over 5 s to measure: route.exposure is [] and an errors[] entry says exposure was not measured; the route is still billed.What refuses a route
| Status | code | When | Billed |
|---|---|---|---|
| 400 | invalid_request | Schema, ranges, an unknown field, more than 25 locations, alternatives with stops, vehicle without truck | No |
| 400 | too_far | The straight-line legs exceed the plan's limit for that mode | No |
| 400 | avoid_too_complex | Avoid zones near this route exceed the plan's zone or vertex cap | No |
| 403 | plan_feature | Truck below Pro, or more locations, alternatives or exposure sets than the plan allows | No |
| 403 | avoid_set_unavailable | An avoid set is missing, off, or not granted to this key | No |
| 422 | no_road_nearby | A location has no routable road within reach | Yes |
| 422 | no_route | No path exists, for example an island with no ferry | Yes |
| 503 | routing_unavailable | The routing engine is not answering | No, or refunded |
GET /route/coverage is public and unmetered: whether routing is answering, which modes exist, what each call costs in lookups, and every plan’s routing caps. Read it rather than hard-coding the numbers. It also prices matrixCell and isochrone, though neither endpoint exists yet — distance matrices and isochrones are planned, and published so they can be budgeted for.
Geocoding
An address in, a point out — built for deliveries, where the question is not only where but how sure. South Africa today; more countries as their data loads. Addresses go in the body, never a URL, and are never logged or stored.
POST https://api.atlasfetch.xyz/geocode
Authorization: Bearer <key>
Content-Type: application/json
{ "q": "25 Adderley Street, Cape Town" }Send either q (one line, up to 300 characters) or address (housenumber, street, suburb, city, postcode, unit). A form that already splits the address should send address: it matches better.
| Field | Meaning |
|---|---|
| country | ISO 3166-1 alpha-2. A hard filter: only that country is searched |
| region | ISO 3166-2 (ZA-WC) or a name. A preference: streets inside it rank first, but a wrong province does not lose the address |
| set | Your boundary sets, matched at the result's point, with each zone's properties |
| base | The lookup's layers at the result's point: country, region, municipal |
| streetgeom | polyline6 or geojson: the matched street's line, trimmed to about 250 m |
Real answer:
{
"result": {
"lat": -33.924455,
"lng": 18.421305,
"precision": "address",
"confidence": 0.96,
"ambiguous": false,
"matched": { "housenumber": "25", "street": "Adderley Street", "locality": "Cape Town", "region": "ZA-WC", "country": "ZA" },
"unmatched": []
},
"errors": []
}precisionis the field to read first.address: that house is in the data.interpolated: between the numbers either side.intersection: a street corner, where the two streets meet, with the second inmatched.crossStreet.site: a named place — a shopping centre, office park, complex, hospital, school, airport or hotel — named inmatched.site.street: the right street, not the house.locality: only the suburb or town.confidenceruns 0 to 1 and ranks answers; it is not a probability.ambiguous: truemeans a same-named street elsewhere scored about as well.matchedis spelt as the data spells it;unmatchedlists what matched nothing.- One best guess, or
result: null— and nothing found is still billed, because the search was done. alternatives: up to 4 other candidates, best first, each shaped as the answer with the same extras, never with alternatives of their own. Always present, and free.Long Street, Cape Townanswers the city centre’s and offers four more in the metro.matched.suburb: the suburb the point lies in, the nearest within about 3 km.matched.localityis the place the caller wrote, so alternatives often share it; the suburb tells them apart (those Long Streets lie in Cape Town City Centre, Observatory, Maitland and so on). Absent atlocalityand where no suburb is near.
What to do with an answer
| The answer | Do |
|---|---|
| address, interpolated, intersection or site, not ambiguous, confidence 0.5 or more | Dispatch (for a site, pass on the unit or shop number) |
| street | The driver has the right street: ask the recipient for a pin or a landmark |
| ambiguous, or locality | Ask the customer before dispatching, offering the answer and its alternatives |
Expect a lot of street in South Africa: OpenStreetMap has house numbers on about 13% of its street names. A misspelling can still land on the right house with less certainty — Cape Tonw finds the same point as above at confidence: 0.59 and ambiguous: true, which the table sends to a person. And a bare place name answers as that place: { "q": "Rondebosch" } gives precision: locality at the suburb itself, which the table also sends to a person — a suburb is not a door.
Street corners
South African addresses are often a corner — Cnr Main Rd and Belmont Rd, Corner of Bree & Strand Streets, Main Rd / Belmont Rd. They answer at the point where the streets meet, and rank with an interpolated house, so dispatch on the same terms. Real output:
{
"result": {
"lat": -33.961421,
"lng": 18.469939,
"precision": "intersection",
"confidence": 0.85,
"ambiguous": false,
"matched": { "street": "Main Road", "crossStreet": "Belmont Road", "locality": "Rondebosch", "region": "ZA-WC", "country": "ZA" },
"unmatched": []
},
"errors": []
}Two streets that never meet answer the first at street, with the second in unmatched. A & B and A / B without “Cnr” count only when both end in a street type, so a business name such as Smith & Sons stays a name.
Named sites
Many delivery addresses name a place rather than a street: a shopping centre, office park, complex, hospital, school, airport or hotel. They answer at a point inside it. Real output for Shop 12, Canal Walk, Century City:
{
"result": {
"lat": -33.892873,
"lng": 18.511114,
"precision": "site",
"confidence": 0.8,
"ambiguous": false,
"matched": { "site": "Canal Walk", "locality": "Century City", "region": "ZA-WC", "country": "ZA" },
"unmatched": []
},
"errors": []
}The unit is not in the answer: “Shop 12” is yours to pass on to the driver, and it is what finds the door inside a mall. A site ranks with a corner, but a house, a corner or an estimated house in the same text still wins over it. Run-together names find it too — Tygervalley Centre, Bellville gives Tyger Valley Centre at confidence: 0.72 — but a place of the same name wins: a bare Tygervalley, Bellville is the suburb Tyger Valley, with the centre among its alternatives. Same-named sites far apart, schools especially, come back ambiguous.
Addresses that cannot be placed
An erf, stand, plot or farm portion (land parcels; there is no cadastre), a township stand, a PO box or a private bag has no point in the data. The part is set aside in unmatched and the answer is the place named, at locality — Erf 1234, Khayelitsha gives Khayelitsha, confidence: 0.36, unmatched: ["erf 1234"]. That is the table’s “ask the customer” row: ask for a street address or a pin. ambiguous: true also marks a near-miss spelling with no house number to confirm it.
POST /geocode/batch: a delivery list
{
"country": "ZA",
"items": [
{ "id": "order-1041", "q": "25 Adderley Street, Cape Town" },
{ "id": "order-1042", "address": { "street": "Long Street", "city": "Cape Town" } },
{ "id": "order-1043", "q": " " }
]
}- Answered in order, each item
found,not_foundorinvalid(with acode), plusbilledandremaining. Invalid items are free. countryandregioncan be set per item;set,baseandstreetgeomapply to the batch.idis your label, echoed back.- A batch always completes. If the allowance runs out partway, every item is still answered and only what was left is charged; the next request is a
402. - Items per request, on the standard plans: 10 (demo), 100 (Personal), 1,000 (Basic and Pro). Over the cap is a
400whosemaxsays what you may send.
Refusals
| Status | code | When | Billed |
|---|---|---|---|
| 400 | invalid_request | The body fails validation, or a batch is over the cap | No |
| 400 | empty_address | q is blank, or address has no street (in a batch: that item) | No |
| 400 | unsupported_country | A named country is not covered; the message lists those that are | No |
| 402 | The allowance was already at 0 | No | |
| 200 | Found, or nothing found | Yes |
unsupported_country message is the live list of covered countries; today it reads Covered: ZA.Public endpoints
No authentication, no metering.
| Method | Path | Purpose |
|---|---|---|
| GET | /health | Liveness |
| GET | /demo/key | The shared public demo key |
| GET | /streets/coverage | Countries that answer base=street, with address counts |
| GET | /route/coverage | Whether routing answers, its modes, the cost in lookups, and each plan's routing caps |
| GET | /payments/plans | Plan catalogue with prices and caps |
| GET | /version | Which API build is serving |
| GET | /openapi.json | OpenAPI 3.1 spec — generate a client from it |
/explore/world and /explore/country/:code served simplified country outlines and were removed on 8 September 2026 — unauthenticated, uncached and expensive (243 KB and 16.8 s per call), they were the one lever an anonymous caller could pull against the database. AtlasFetch 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 says what | No |
| 401 | Missing, malformed or revoked key | No |
| 402 | Monthly allowance exhausted | After reset |
| 403 | Plan does not include this, or a cap exceeded | No |
| 409 | Conflict, e.g. duplicate set name | No |
| 422 | Routes only: no road near a location, or no path. Billed — the route was attempted | No |
| 429 | Per-key rate limit — honour Retry-After | Yes |
| 503 | Routes only: the routing engine is not answering. Not billed | In a few minutes |
Rate limiting is per key, per second. Quota is per account, per month. Route errors also carry a machine-readable code beside error: invalid_request, too_far, avoid_too_complex, plan_feature, avoid_set_unavailable, no_road_nearby, no_route, routing_unavailable. Geocoding adds empty_address and unsupported_country.
Privacy and data
We do not store the coordinates you look up. Usage metering records the endpoint, the key and a timestamp — never the point. When a request comes from an official AtlasFetch client such as the MCP server, that client’s name and version are recorded too (for example atlasfetch-mcp/0.1.4); any other User-Agent is ignored and never stored.
Reference boundaries derive from OpenStreetMap under the ODbL. Attribution and share-alike obligations pass through to you — see data attribution. House-level answers also draw on municipal address points. In Cape Town: Contains address data from the City of Cape Town Open Data Portal, © City of Cape Town. Also contains address data from these municipalities, © each municipality: eThekwini Municipality (Durban); City of Tshwane (Pretoria); City of Ekurhuleni; Nelson Mandela Bay Municipality (Gqeberha); George Municipality; Langeberg Municipality (Robertson, Montagu, Ashton, Bonnievale, McGregor). AtlasFetch is not affiliated with or endorsed by any of these municipalities. Your own uploaded boundaries are private to your account and are never returned to anyone else.
Try it without writing any code
The playground loads a demo key automatically and shows the exact request URL.
Open the playgroundUse it inside Claude Code
claude mcp add atlasfetch -- npx -y atlasfetch-mcpWithout a key it uses the shared demo key, 1,000 lookups a month across everyone. Add your own with -e ATLASFETCH_API_KEY.