Get the nearest street and house number from a coordinate

You have a coordinate — a GPS fix, a driver’s last ping, a tap on a map — and you need the street it sits on, ideally with the house number. One request answers it, three times over: within 5 m, within 20 m, and at any distance. This guide shows why three, and which one to use.

Examples run against the live API on 1 October 2026.

Is this the right tool?

This answers “which street is this point on?” from a point-in-polygon and nearest-line lookup over OpenStreetMap data, worldwide. It is not a full address geocoder.

  • Yes: labelling a GPS trace with street names, telling a dispatcher where a driver stopped, showing a human-readable location next to a pin, picking the likely building from a delivery coordinate, deduplicating pings by street.
  • Yes: you want it in the same call as the country, region and municipality — see reverse geocoding to a municipality.
  • No: an address to a coordinate. That is forward geocoding, which AtlasFetch does not do.
  • No: a complete, validated postal address for every point. House numbers exist where OpenStreetMap maps them, which varies by country — see house numbers below and the comparison with other providers.

1. Get an API key

To try it without signing up, fetch the shared demo key. It is capped at 1,000 lookups a month across everyone using it, so use it for experiments only.

shell
export ATLASFETCH_KEY=$(curl -s https://api.atlasfetch.xyz/demo/key | jq -r .key)

For your own limits, sign up free and create a key in the dashboard. Keys are shown once and stored hashed.

2. Ask for the street layer

The street layer is opt-in: add street to base. The default country,region,municipal does not include it, and asking for it costs nothing extra — one call is one lookup however many layers it touches.

curl
curl -H "Authorization: Bearer $ATLASFETCH_KEY" \
  "https://api.atlasfetch.xyz/location/lookup?lat=51.5034&lng=-0.1276&base=street"
JavaScript (Node 18+, or any runtime with fetch)
const res = await fetch(
  "https://api.atlasfetch.xyz/location/lookup?lat=51.5034&lng=-0.1276&base=street",
  { headers: { Authorization: `Bearer ${process.env.ATLASFETCH_KEY}` } },
);
if (!res.ok) throw new Error(`AtlasFetch ${res.status}: ${(await res.json()).error}`);

const { base } = await res.json();
// The widest answer that found something, else nothing is nearby at all.
const answer = [...(base.street ?? [])].reverse().find(a => a.streetName) ?? null;
console.log(answer?.streetNumber, answer?.streetName, answer?.postcode);
// 10 Downing Street SW1A 2AA
Python (requests)
import os
import requests

res = requests.get(
    "https://api.atlasfetch.xyz/location/lookup",
    params={"lat": 51.5034, "lng": -0.1276, "base": "street"},
    headers={"Authorization": f"Bearer {os.environ['ATLASFETCH_KEY']}"},
    timeout=10,
)
res.raise_for_status()

street = res.json()["base"]["street"] or []
answer = next((a for a in reversed(street) if a["streetName"]), None)
print(answer and (answer["streetNumber"], answer["streetName"], answer["postcode"]))
# ('10', 'Downing Street', 'SW1A 2AA')

3. Read the three answers

base.street is always an array of three entries, in this order: the best answer within 5 m, within 20 m, and at any distance. Downing Street, where all three agree:

response · 200 (lat=51.5034&lng=-0.1276&base=street)
{
  "base": {
    "street": [
      { "radiusMeters": 5,    "streetNumber": "10", "streetName": "Downing Street", "postcode": "SW1A 2AA", "distanceMeters": 0 },
      { "radiusMeters": 20,   "streetNumber": "10", "streetName": "Downing Street", "postcode": "SW1A 2AA", "distanceMeters": 0 },
      { "radiusMeters": null, "streetNumber": "10", "streetName": "Downing Street", "postcode": "SW1A 2AA", "distanceMeters": 0 }
    ]
  },
  "sets": {},
  "errors": []
}
FieldTypeWhat it is
radiusMeters5, 20 or nullWhich search this entry answers. null is the unlimited one, always last.
streetNumberstring or nullThe house number, as mapped: 44A and 12-14 are real values, so treat it as text, never an integer.
streetNamestring or nullThe street. In its own language and script — Shibuya answers 道玄坂. A road known only by a route number answers with that (N1).
postcodestring or nullComes with a matched house number, not with a street on its own. Keep it as text: 0181 must not lose its leading zero.
distanceMetersnumber or nullTo the building (0 when the point is inside it) or to the nearest point of the street, to 0.1 m.

How each entry is chosen: a house number within the radius wins, even over a nearer street; otherwise the nearest named street; otherwise nothing. Unnamed roads are never an answer. For the unlimited entry the house number must still be within 20 m — beyond that you get the street alone, because a building 300 m away is not where the point is.

4. Which radius should you use?

The three exist because “the nearest street” depends on how far you are willing to look, and only you know how accurate your coordinate is. Times Square, where they disagree:

response · 200 (lat=40.758&lng=-73.9855&base=street)
"street": [
  { "radiusMeters": 5,    "streetNumber": null,   "streetName": "7th Avenue", "postcode": null,  "distanceMeters": 0.5 },
  { "radiusMeters": 20,   "streetNumber": "1540", "streetName": "Broadway",   "postcode": "10036", "distanceMeters": 14.5 },
  { "radiusMeters": null, "streetNumber": "1540", "streetName": "Broadway",   "postcode": "10036", "distanceMeters": 14.5 }
]

The point is half a metre from 7th Avenue and 14.5 m from a numbered Broadway address. Both are true. Which you want depends on the question:

EntryReads asUse it when
radiusMeters: 5The street this point is on, if anyThe coordinate is trusted — a surveyed location, a map click. Strict enough to return nothing rather than guess.
radiusMeters: 20The building this point belongs toA delivery or visit coordinate, where the pin is near the door but not on the carriageway. The usual choice.
radiusMeters: nullThe nearest street, wherever it isLabelling a point for a human. Always check distanceMeters before showing it — 114 m away is context, not an address.
Pick per use, not per app. A phone GPS fix in a city is routinely 10–20 m out, so the 5 m entry being null says more about the fix than about the place.

5. House numbers depend on the country

Street names are mapped nearly everywhere OpenStreetMap has roads. House numbers are a separate effort, done thoroughly in some countries and barely in others. Real answers, same request, five places:

CoordinateWidest answerNumberPostcode
London, Downing Street10 Downing StreetYesSW1A 2AA
Berlin, Pariser Platz1 Pariser PlatzYes10117
New York, Times Square1540 BroadwayYes10036
Cape Town, Adderley StreetAdderley StreetNo — streetNumber: nullnull
Tokyo, Shibuya道玄坂No — streetNumber: nullnull
Design for the street-only case. A UI that prints `${streetNumber} ${streetName}` shows null Adderley Street to half the world. And postcode arrives only with a number: there is no postcode lookup here.

6. Two kinds of null, and they mean different things

An entry of nulls: nothing within that radius

The middle of Hyde Park is in a covered country, on no street. The 5 m and 20 m searches find nothing; the unlimited one reaches a path 114 m away:

response · 200 (lat=51.5073&lng=-0.1657&base=street)
"street": [
  { "radiusMeters": 5,    "streetNumber": null, "streetName": null,                "postcode": null, "distanceMeters": null },
  { "radiusMeters": 20,   "streetNumber": null, "streetName": null,                "postcode": null, "distanceMeters": null },
  { "radiusMeters": null, "streetNumber": null, "streetName": "Policeman's Walk",  "postcode": null, "distanceMeters": 114.1 }
]

The layer itself null: the point is in no country

street: null — not an array — means the point lies outside every country the layer covers. In practice that is the sea, or Antarctica:

response · 200 (lat=0&lng=-30&base=country,street)
{ "base": { "country": null, "street": null }, "sets": {}, "errors": [] }

So check the layer before indexing into it. base.street[0] throws on an ocean coordinate; (base.street ?? []) does not.

7. One call, street and administrative layers together

Ask for everything at once. It is still one lookup, and the street arrives beside the codes you store:

response · 200 (base=country,region,municipal,street)
{
  "base": {
    "country":   { "code": "ZA",     "name": "South Africa" },
    "region":    { "code": "ZA-WC",  "name": "Western Cape" },
    "municipal": { "code": "B-ZA-1", "name": "City of Cape Town" },
    "street": [
      { "radiusMeters": 5,    "streetNumber": null, "streetName": "Adderley Street", "postcode": null, "distanceMeters": 2.9 },
      { "radiusMeters": 20,   "streetNumber": null, "streetName": "Adderley Street", "postcode": null, "distanceMeters": 2.9 },
      { "radiusMeters": null, "streetNumber": null, "streetName": "Adderley Street", "postcode": null, "distanceMeters": 2.9 }
    ]
  },
  "sets": {},
  "errors": []
}

Add set= to test the same point against your own polygons in the same request — delivery zones, service areas, franchise territories. See custom geofences.

What it will not do

  • No forward geocoding. There is no address-to-coordinate direction, and no place or street search by name.
  • No street geometry. You get the name, number, postcode and distance — not the road’s line, and not the building’s outline.
  • One coordinate per request. There is no batch endpoint; send one lookup per point.
  • Not an address validator. The answer is the nearest mapped thing, not proof that a delivery address is correct.
  • OpenStreetMap only, under the ODbL — attribution and share-alike obligations pass through to you.

Errors and billing

One request is one lookup, whether it resolves one layer or all four, and metering happens before matching — so a point at sea still counts. Rejected requests do not:

StatusMeaningBilled?
200Answered. errors[] is always present and lists anything skipped.Yes, even if nothing matched
400Invalid parameters — an unknown layer in base, a latitude out of rangeNo
401Missing, malformed or revoked keyNo
402Monthly allowance used upNo
429Per-key rate limit; honour Retry-AfterNo

X-Lookups-Remaining on every answer tells you what is left this month. The coordinates you send are not stored.

Next

Using an AI assistant? The AtlasFetch MCP server lets Claude, Cursor and other clients run these lookups directly, street layer included. Machine-readable reference: llms.txt. Every parameter and field: API reference.

Data

Streets, house numbers and reference boundaries derive from OpenStreetMap under the ODbL; attribution and share-alike obligations pass through to you. See data attribution.