wayfindnow

API reference

Address autocomplete, place search, reverse and batch geocoding over 477 million addresses and 67 million places in 198 countries. Eight endpoints, one credential, billed by the address, not the keystroke.

Quickstart

One key, one endpoint, one session token reused while the user types. This is the same /autocomplete example documented in full below -- it is repeated here because it is the single most-used part of this page and the one thing worth reading before anything else: session is the billing unit.

# `session` groups every keystroke of ONE address lookup into a SINGLE
# billable session -- reuse the same token on each call, drop it once the
# user picks a result. See "Sessions and what you are billed for" below.
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://wayfindnow.com/autocomplete?q=Damrak+1+Amsterdam&session=9f2e1c-a-user-picked-uuid"
// `session` is the billing unit: reuse the SAME token on every keystroke of one
// address lookup and the whole lookup is billed ONCE, not once per request.
const res = await fetch(
  "https://wayfindnow.com/autocomplete?q=Damrak+1+Amsterdam&session=" + sessionToken,
  { headers: { Authorization: "Bearer " + apiKey } }
);
const rows = await res.json();   // [] on no matches -- that is not an error
import requests

# `session` groups every keystroke of ONE address lookup into a SINGLE billable session.
# Mint one token (any UUID) per lookup, reuse it on every call, then start a new one.
r = requests.get(
    "https://wayfindnow.com/autocomplete",
    params={"q": "Damrak 1 Amsterdam", "session": session_token},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
rows = r.json()

Your key is on your account page under "Your API key"; named keys with their own limits live under API keys. Full reference for every endpoint is in the left nav: /autocomplete, /search, /nearby, /place/{id}, /chose, /reverse, /parse and /batch.

Authentication

Send your key as a bearer token: Authorization: Bearer YOUR_API_KEY. If you cannot set headers, ?key=YOUR_API_KEY in the query string works the same way -- prefer the header where you can, since query strings end up in access logs.

Every endpoint on this page requires a valid key. A missing or unrecognised one gets a plain 401 (not JSON -- see Errors below). /health is the one route that stays open, for load balancers that cannot hold a credential.

Sessions and what you are billed for

The unit that is billed is a session, not a request. Someone typing "Rotterdam" fires an /autocomplete call after almost every keystroke -- that is normal and expected, and it is not eight charges. Generate one token per address lookup (a UUID you make up client-side is fine) and send it as &session= on every call that belongs to that lookup: every keystroke's autocomplete request, and the request that resolves whichever suggestion got picked. All of it counts as one session.

The token is opaque to us on purpose -- send a UUID, not something that identifies a person. It is hashed the moment it arrives and only the hash is kept; the raw value is never logged or returned.

A session is not open-ended: one session covers up to 30 requests, which is roughly four times what a real address lookup takes. Anything past that is billed per request, so reusing a single token for unrelated lookups costs the same as not sending one at all. Use a fresh token per address and you will never meet this limit.

Your account page shows sessions (the billable count) alongside requests and requests_per_session -- the last one is "how many keystrokes does one address cost you", which is the number worth watching if you are migrating pricing expectations from another provider.

session is not exclusive to /autocomplete: it is read once, before routing, so any request to any endpoint that carries the same token is folded into the same session. If a lookup happens to span /autocomplete and a /search or /nearby call, pass the same token to all of them and it is still one session.

The session is a discount you earn by finishing the lookup

One rule, three cases. It is the same rule Google Places applies to an abandoned session, so if you are porting an integration the behaviour will already be familiar:

What happenedWhat is billed
session sent, and a suggestion was resolved (/chose or /place/) one session, however many keystrokes it took
session sent, nothing ever resolved every request, individually
no session at all every request, individually

So sending the token is worth doing, and closing the loop with /chose when the user picks is what actually converts the discount. Omitting the token still works -- nothing breaks, you simply pay per request.

Choosing what comes back: fields

By default a suggestion carries what a dropdown needs: id, main_text, secondary_text, text, type, kind, the matched spans, and cc -- the two-letter country of the row. Keep cc: passing it back as country= on /place/{id} is the difference between a 0.06s lookup and a 56s one. Coordinates and the structured address are opt-in:

fieldsadds
(omitted)the display set above
fields=geometrylocation — lat/lon
fields=addresscomponents — the address in parts, see below
fields=geometry,addressboth
fields=*the whole record

A name we have not shipped yet is ignored rather than rejected, so adding a tier to your client is always safe. id is always returned — it is the one field you may store indefinitely (see below).

The address in parts: components

A bill of lading has one box for the street and a different box for the state. These are document fields, copied — never sliced back out of the rendered label, which would break the day we change a separator.

keywhat it isexample
streetthe street or road nameDamrak
house_numberas written locally, ranges and letters included1-5, 12A
sublocalitya part of a town — quarter, hamlet—
localitythe town or cityAmsterdam
subregionthe tier between town and region — county, département, provincia. Not yet returned, see below—
regionthe top subdivision — the "State / Province" box Florida, Île-de-France, 東京都
postcodepostal code1012LG
countrycountry nameNetherlands
country_codeISO 3166-1 alpha-2NL

A key is absent when we do not hold that tier, never guessed. The absence is information: no house_number means the row is a street rather than an address, and a missing region means we could not establish one for this address — not that the country has no states. Nothing is ever back-filled from the tier above, because a guessed province on a customs form is worse than a blank one.

subregion is named here and does not yet appear in any response. We hold 47,593 of those areas as records, but they are not yet linked to the towns inside them, so no address can reach one. The name is reserved so it can start appearing without a breaking change; write your client to tolerate its absence, which is the same rule every key here follows.

admin_area is deprecated. It is the old name for region and still carries the identical value. Move to region; admin_area will be removed in a future version.

Coming from Google

The mapping is mechanical. We name each part by the role it plays rather than by its depth in the hierarchy, because depth is the one thing you cannot act on: administrative_area_level_1 is a state in the US, a région in France and a constituent country in the UK, so reading it still means knowing the country first.

Googlehere
street_numberhouse_number
routestreet
sublocalitysublocality
localitylocality
administrative_area_level_2subregion
administrative_area_level_1region
postal_codepostcode
countrycountry + country_code

If you need the depth as a number rather than a name — walking the hierarchy generically rather than filling a form — /place/{id} returns admin_chain, where every rung carries both level (the name above) and level_number (1 country, 2 region, 3 subregion, 4 locality, 5 sublocality).

What you may store

Store the id, refresh the record. Our id is stable and yours to keep for as long as you like — put it in your orders table and re-resolve it whenever you need the address. The rest of a response may be cached for up to 30 days. Building a persistent copy of the database is not permitted.

This is better for you as well as for us: an address that is re-resolved stays correct when a street is renamed or a postcode is reallocated.

Locking a key down

A browser key is published by design — it ships in your page source. Every key can therefore be restricted, and an unrestricted key behaves exactly as it always has:

restrictionrefuses
allowed originsrequests from any site but yours (https://shop.example.com, https://*.example.com)
allowed IPsserver-side calls from anywhere but your machines (single addresses or CIDR)
allowed endpointsa widget key reaching /batch

A refusal is 403, not 401, and the message names which list refused — the common case is that you have just locked a key down and need to know which one to widen.

Be clear on what an origin restriction is worth: browsers send Origin, and anything that is not a browser can forge it. It stops your key working on somebody else's website. It does not stop a determined attacker with a shell.

Language: lang and Accept-Language

Two independent things share the word "language" here, and it is worth keeping them apart:

/nearby has no query text to detect a script from, so with no lang given it defaults to plain en -- Accept-Language still applies if you send it, only the query-script fallback is unavailable there.

The country filter

An optional two-letter code (NL, BG, ...) that scopes the search to one country's index instead of all 198 -- faster, and it removes cross-country ambiguity for short names. All three endpoints accept it. On /autocomplete, if you do not pass it, a country named inside the query itself ("Sofia Bulgaria") can still scope the search the same way, on top of everything you would get for free otherwise.

Rate limits

Each key is a token bucket refilled continuously at your configured requests/second (20 req/s is the default for a key with no custom rate configured), with a burst allowance of 3 seconds' worth of tokens -- typing five characters in a second is normal and will not be throttled. Once the bucket is empty you get 429 with a Retry-After: 1 header and no body; wait a second and retry.

Errors

Error responses are plain HTML error pages (the Python stdlib's default), not JSON -- with one exception, 402, which is JSON. Do not .json() a response blindly. Check the status code first.

statuswhenwhat to do
400 a required parameter is missing or malformed (q on /search, lat/lon on /nearby, a non-integer limit), or a kind you asked for is not one we recognise. fix the parameter named in the response body and retry. For an unknown kind the body lists every valid value, so it is worth validating client-side before the call rather than round-tripping to find out.
401 missing or unrecognised API key. check that Authorization: Bearer ... (or ?key=) carries a real key from your account page. Retrying with the same key will not succeed -- that is not a transient failure.
404 the path itself does not exist. check the endpoint path and method. A query that legitimately finds nothing is not this -- that is 200 with an empty result, see below.
402 this account's prepaid credit is used up. The one error that is JSON: {"error": "credit exhausted", "detail": ...}. add credit on your account page. Waiting does not help and retrying does not either -- an empty balance does not refill on its own, which is why this is not a 429.
403 the key is genuine but a restriction on it refused this call -- see Locking a key down above. The body names which list (origins, IPs or endpoints) refused. widen that list on the API keys page. Do not go looking for a different key; the credential is fine.
413 a /batch call carried more than 500 addresses. split the list.
429 two different things, told apart by Retry-After. 1: this key's token bucket is empty -- see Rate limits above. 3600: a hard-capped plan is over its cap for the billing period. wait for the interval in the header and retry once. Retrying immediately in a tight loop just keeps the bucket empty; for the hourly value, nothing you send before then will be served.
503 /place/{id} could not reach the database in time. Sent with Retry-After: 2 and no body. retry once after two seconds. If you called without country=, add it (see /place/{id}) -- the unhinted lookup is the one that times out.

A search that simply finds nothing is not an error -- it is 200 with an empty list (or, for /search, a results: [] with a reason).

GET /autocomplete

Address, street and place suggestions as someone types. Examples below use the public host, https://wayfindnow.com. The try-it panel does not hard-code that -- it calls relative to wherever this page is served, so it keeps working if the host changes. If you have an older integration pointed at https://wayfindnow.com, move it: that form is retired, and it sends your key over plain HTTP.

# `session` groups every keystroke of ONE address lookup into a SINGLE
# billable session -- reuse the same token on each call, drop it once the
# user picks a result. See "Sessions and what you are billed for" below.
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://wayfindnow.com/autocomplete?q=Damrak+1+Amsterdam&session=9f2e1c-a-user-picked-uuid"
// `session` is the billing unit: reuse the SAME token on every keystroke of one
// address lookup and the whole lookup is billed ONCE, not once per request.
const res = await fetch(
  "https://wayfindnow.com/autocomplete?q=Damrak+1+Amsterdam&session=" + sessionToken,
  { headers: { Authorization: "Bearer " + apiKey } }
);
const rows = await res.json();   // [] on no matches -- that is not an error
import requests

# `session` groups every keystroke of ONE address lookup into a SINGLE billable session.
# Mint one token (any UUID) per lookup, reuse it on every call, then start a new one.
r = requests.get(
    "https://wayfindnow.com/autocomplete",
    params={"q": "Damrak 1 Amsterdam", "session": session_token},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
rows = r.json()
paramrequireddefault meaning
qyes—the text typed so far.
sessionnononegroups this call into a billable session, see above.
countrynoautoa two-letter code to scope the search to one country.
langnoautotwo-letter code; see Language above.
lat, lonnononesend both to bias ranking toward this point (a Gauss decay, not a filter) -- a distant match still ranks, just lower. This is different from /nearby's lat/lon, which is a hard search radius.

There is no limit parameter on this endpoint -- it always returns up to 10 rows. (Verified live: adding &limit=2 to a query that otherwise returns 10 rows still returns 10.) If you need a different count, that is what /search and /nearby are for.

Response

A JSON array, best match first. Each row:

paramrequireddefault meaning
idthe entity's id. Stable; safe to use as a React key.
typeaddress, street, locality or place.
kinda category (port, airport, ...) for place rows, null otherwise.
text, main_text, secondary_textready-to-render label, split the way Google's autocomplete widget splits it (bold part / grey part).
matched[[start, length], ...] spans into main_text -- the part that justifies the match, for highlighting it.
ccthe row's two-letter country. Always present. Pass it back as country= when you call /place/{{id}}.
components<em>opt-in</em>only with fields=address: the address broken into parts -- street, house_number, locality, region, postcode, country, country_code. Each key is present only when that entity actually has it, so its absence is itself information. Full list and the mapping from Google's names: Address components.
location<em>opt-in</em>only with fields=geometry: {{"lat":..,"lon":..}}, when known.

Real response, captured live and trimmed to two rows:

[
  {"id": "a604088905", "type": "address", "kind": null,
   "text": "Damrak 1-5, 1012LG Amsterdam, Netherlands",
   "main_text": "Damrak 1-5", "secondary_text": "1012LG Amsterdam, Netherlands",
   "matched": [[0, 6]], "cc": "NL"},
  {"id": "a130895906", "type": "address", "kind": null,
   "text": "Damrak 1, 1012LG Amsterdam, Netherlands",
   "main_text": "Damrak 1", "secondary_text": "1012LG Amsterdam, Netherlands",
   "matched": [[0, 6]], "cc": "NL"}
]

A named place plus an optional category, when you have a place name but not coordinates -- "ports near Rotterdam" without knowing Rotterdam is 51.9244, 4.4777. Under the hood this resolves q with the same logic as /autocomplete, then runs /nearby around whatever it resolved to.

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://wayfindnow.com/search?q=Rotterdam&kind=port&kind=pier&limit=5"
const res = await fetch(
  "https://wayfindnow.com/search?q=Rotterdam&kind=port&kind=pier&limit=5",
  { headers: { Authorization: "Bearer " + apiKey } }
);
const { query, near, results } = await res.json();
import requests

r = requests.get(
    "https://wayfindnow.com/search",
    params=[("q", "Rotterdam"), ("kind", "port"), ("kind", "pier"), ("limit", 5)],
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = r.json()   # {"query", "near", "results"}
paramrequireddefault meaning
qyes—the place name to resolve, e.g. Rotterdam.
kindnoanyrepeat the parameter per kind you want, e.g. &kind=port&kind=pier -- a single comma-joined value will not match anything and returns 400. Valid values: port, pier, marina, airport, airport_terminal, rail_terminal, road_terminal, multimodal_terminal, border_crossing, warehouses, shipping_center, freight_and_cargo_service, freight_forwarding_agency, transportation, wholesaler, business, business_manufacturing_and_supply, commercial_industrial, company, corporate_office, professional_services, industrial_company, industrial_equipment, location.
radiusno25kman Elasticsearch distance string, e.g. 10km.
limitno20capped at 100; non-integer values are a 400.
countrynononesee the country filter above.
rank_bynoprominenceprominence (importance, then distance) or distance (nearest first).
langnoautosee Language above.
sessionnononealso honored here, not just on /autocomplete -- see Sessions & billing below. A call carrying the same token as an earlier /autocomplete call in the same lookup still counts toward that one session.

Response

An object, not an array:

paramrequireddefault meaning
queryyour q, echoed back.
nearthe row q resolved to -- same shape as an /autocomplete row (it comes from that endpoint), or null if nothing could be resolved.
resultsan array in /nearby's shape (below) -- note these rows carry distance_m but not matched.
reasonpresent only when near is null, explaining why.

Real response, captured live and trimmed to two results:

{
  "query": "Rotterdam",
  "near": {"id": "l75957", "type": "locality", "kind": null,
           "text": "Rotterdam, Netherlands", "main_text": "Rotterdam",
           "secondary_text": "Netherlands", "matched": [[0, 9]],
           "components": {"locality": "Rotterdam", "country_code": "NL"},
           "location": {"lat": 51.9244424, "lon": 4.47775}},
  "results": [
    {"id": "p224789", "type": "place", "kind": "port",
     "text": "Rotterdam \u2014 Rotterdam, Netherlands", "main_text": "Rotterdam",
     "secondary_text": "Rotterdam, Netherlands",
     "components": {"locality": "Rotterdam", "country_code": "NL"},
     "distance_m": 2744.7, "location": {"lat": 51.9, "lon": 4.483333}},
    {"id": "p224823", "type": "place", "kind": "port",
     "text": "Schiedam \u2014 Pernis Rotterdam, Netherlands", "main_text": "Schiedam",
     "secondary_text": "Pernis Rotterdam, Netherlands",
     "components": {"locality": "Pernis Rotterdam", "country_code": "NL"},
     "distance_m": 5985.7, "location": {"lat": 51.9, "lon": 4.4}}
  ]
}

GET /nearby

What is within a radius of a point you already have coordinates for -- a driver's GPS ping, a warehouse's stored lat/lon. Restricted to type=place rows: addresses and streets carry no kind, so a plain radius search over them would bury the two ports you wanted under ten thousand houses.

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://wayfindnow.com/nearby?lat=51.9244&lon=4.4777&radius=5km&kind=port"
const res = await fetch(
  "https://wayfindnow.com/nearby?lat=51.9244&lon=4.4777&radius=5km&kind=port",
  { headers: { Authorization: "Bearer " + apiKey } }
);
const rows = await res.json();
import requests

r = requests.get(
    "https://wayfindnow.com/nearby",
    params={"lat": 51.9244, "lon": 4.4777, "radius": "5km", "kind": "port"},
    headers={"Authorization": "Bearer YOUR_API_KEY"},
)
rows = r.json()
paramrequireddefault meaning
lat, lonyes—the search centre. Missing or non-numeric is a 400 -- unlike /autocomplete's lat/lon, this is a hard filter, not a ranking bias.
kindnoanysame repeated-parameter rule as /search's kind. Valid values: port, pier, marina, airport, airport_terminal, rail_terminal, road_terminal, multimodal_terminal, border_crossing, warehouses, shipping_center, freight_and_cargo_service, freight_forwarding_agency, transportation, wholesaler, business, business_manufacturing_and_supply, commercial_industrial, company, corporate_office, professional_services, industrial_company, industrial_equipment, location.
radiusno5kman Elasticsearch distance string.
limitno20capped at 100.
countrynononesee the country filter above.
rank_bynodistancedistance (nearest first) or prominence (importance, then distance).
langnoenno query text here to detect a script from, so this defaults to English rather than auto-detecting -- see Language above.
sessionnononealso honored here, not just on /autocomplete -- see Sessions & billing below. A call carrying the same token as an earlier /autocomplete call in the same lookup still counts toward that one session.

Response

A JSON array, same row shape as /autocomplete's minus matched, plus distance_m (metres, from your lat/lon):

Real response, captured live:

[
  {"id": "p224789", "type": "place", "kind": "port",
   "text": "Rotterdam \u2014 Rotterdam, Netherlands", "main_text": "Rotterdam",
   "secondary_text": "Rotterdam, Netherlands",
   "components": {"locality": "Rotterdam", "country_code": "NL"},
   "distance_m": 2740.5, "location": {"lat": 51.9, "lon": 4.483333}}
]

GET /place/{id}

The full record behind a suggestion: coordinates, postcode, the admin chain with populations, and the locality's name in every language we hold. This is the second half of every lookup -- /autocomplete gives you a thin row to draw a dropdown from, and the pick fetches this. It is also what converts a session (see Sessions above): a call here carrying the lookup's session token is what turns eight billed keystrokes into one billed address.

curl -H "Authorization: Bearer YOUR_API_KEY" \\\n  "https://wayfindnow.com/place/a604088905?country=NL&session=9f2e1c-a-user-picked-uuid"
paramrequireddefault meaning
idyesin the path. The id from any suggestion row -- a… address, s… street, l… locality, p… place.
countrynosend it. The cc from the suggestion you are opening. The database is partitioned by country and the primary key leads with it: with this hint the lookup is one index read (~0.06s); without it every partition is scanned (measured 56s, and the 503 below). A wrong value costs nothing -- it falls back to the slow path -- so it is a hint, never trusted for correctness.
sessionnothe same token you sent on the keystrokes. This is the call that converts them into one billed session.

Response

One object. type is the entity kind; admin_chain is innermost first; locality_names is keyed by language code. Address rows carry street/house_number/postcode; place rows carry a place object with the name and category instead. A missing id is 404; a lookup that cannot reach the database in time is 503 with Retry-After: 2.

Real response, captured live and trimmed:

{
  "id": 604088905, "type": "address", "country": "NL",
  "street": "Damrak", "house_number": "1-5", "postcode": "1012LG",
  "location": {"lat": 52.3772801, "lon": 4.8976771},
  "admin_chain": [
    {"level": "locality", "level_number": 4, "id": 71786, "ext_id": "NL-l-amsterdam",
     "name": "Amsterdam", "population": 881933},
    {"level": "region", "level_number": 2, "id": 75087, "ext_id": "NL-r-noord-holland",
     "name": "Noord-Holland", "population": 2952000}
  ],
  "locality_names": {"names": {"en": ["Amsterdam"], "nl": ["Amsterdam"],
                               "ar": ["آمستردام"], "ja": ["アムステルダム"]}},
  "street_names": ["Damrak"],
  "updated_at": "2026-08-30T23:32:18+02:00"
}

GET /chose

Tell us which suggestion the user picked. Two things happen: the lookup's session converts (the discount in Sessions above -- this and /place/ are the only two calls that do that), and the choice becomes ranking feedback. The second is why rank matters more than id: "picked the third row" says the first two were worse for this query, and that is the signal we train on.

curl -H "Authorization: Bearer YOUR_API_KEY" \\\n  "https://wayfindnow.com/chose?q=Damrak+1&id=a604088905&rank=2&session=9f2e1c-a-user-picked-uuid"
paramrequireddefault meaning
sessionnothe lookup's token. Without it nothing converts -- the call is still accepted, it just changes nothing on your bill.
idnothe chosen row's id.
ranknoits 1-based position in the list the user saw.
qnowhat the user had typed when they picked.
countrynothe country filter in force, if any.

Response

Always 200 with the same body -- there is nothing to look up:

{"ok": true}

Every parameter is optional and none is validated, so a typo here fails silently rather than loudly. If the session is not converting, check the session token matches the one on your /autocomplete calls exactly.

GET /reverse

What is at a coordinate -- a GPS ping, a map click. Returns the places nearest the point, nearest first, in /nearby's row shape. Unlike /nearby it takes no kind: it answers "what is here", not "which ports are here".

curl -H "Authorization: Bearer YOUR_API_KEY" \\\n  "https://wayfindnow.com/reverse?lat=52.3771&lon=4.898"
paramrequireddefault meaning
lat, lonyesthe point. Missing or non-numeric is a 400.
radiusno500man Elasticsearch distance string.
countrynosee the country filter above.
langnoenno query text to detect a script from, so this defaults to English -- see Language above.

Response

A JSON array, nearest first, with distance_m from your point. components and location are always present here -- this endpoint does not take fields=.

Real response, captured live and trimmed to one row:

[
  {"id": "p23530308", "type": "place", "kind": "shopping",
   "text": "2020 — Damrak 1, 1012LG Amsterdam, Netherlands",
   "main_text": "2020", "secondary_text": "Damrak 1, 1012LG Amsterdam, Netherlands",
   "components": {"street": "Damrak", "house_number": "1", "locality": "Amsterdam",
                  "postcode": "1012LG", "country_code": "NL"},
   "distance_m": 1.8, "location": {"lat": 52.37709, "lon": 4.89798}}
]

GET /parse

Resolve a whole pasted address rather than a typed prefix. The difference from /autocomplete is the first step: a country named at either end of the text ("…, Netherlands", "Italy, …") is peeled off and used as the filter, instead of being treated as a word that has to match something. After that it is the same matcher, so a query that works on one works on the other.

curl -H "Authorization: Bearer YOUR_API_KEY" \\\n  "https://wayfindnow.com/parse?q=Damrak+1+Amsterdam,+Netherlands"
paramrequireddefault meaning
qyesthe full address text.
countrynoautooverrides whatever was detected in the text.
langnoautosee Language above.

Response

An object: country is what was peeled off (or null), query is the text that remained, results is a list in /autocomplete's row shape, best first, with components and location always present. Nothing found is results: [], not an error.

Real response, captured live and trimmed to one result:

{
  "country": null,
  "query": "Damrak 1 Amsterdam",
  "results": [
    {"id": "a604088905", "type": "address", "kind": null,
     "text": "Damrak 1-5, 1012LG Amsterdam, Netherlands",
     "main_text": "Damrak 1-5", "secondary_text": "1012LG Amsterdam, Netherlands",
     "matched": [[0, 6]],
     "components": {"street": "Damrak", "house_number": "1-5", "locality": "Amsterdam",
                    "postcode": "1012LG", "country_code": "NL"},
     "location": {"lat": 52.3772801, "lon": 4.8976771}, "cc": "NL"}
  ]
}

GET /batch

A spreadsheet of consignees, resolved in one call instead of five hundred round trips. Repeat q once per line; up to 500 per call (413 past that). Lines are resolved concurrently, so 500 of them take seconds, not minutes.

curl -H "Authorization: Bearer YOUR_API_KEY" \\\n  "https://wayfindnow.com/batch?q=Damrak+1+Amsterdam&q=Blaak+31+Rotterdam&country=NL"
paramrequireddefault meaning
qyesrepeat per address, e.g. &q=…&q=…. At most 500.
countrynoapplied to every line. Send it when you know it -- it is the cheapest way to keep a Dutch spreadsheet from matching a street in Texas.
langnoensee Language above.

Response

An array, one object per input line in the same order: query echoed, country as detected or given, and match -- the single best row in /parse's shape -- or null with a reason when nothing matched at all.

Read match as the top suggestion, not a verified resolution. There is no confidence score. ambiguous: true means only that a second candidate existed; it does not say the first is right. Measured on a nonsense line ("Nowhere Street 999999 Zzz"): the best match was a café in New York named NOWHERE, with ambiguous: true -- fuzzy search will find something for almost any text. If a wrong fill is worse than an empty cell for you, treat ambiguous: true as "review this row" and consider country= mandatory.

Real response, captured live -- the second line is that nonsense query, left in on purpose:

[
  {"query": "Damrak 1 Amsterdam", "country": null, "ambiguous": true,
   "match": {"id": "a604088905", "type": "address",
             "text": "Damrak 1-5, 1012LG Amsterdam, Netherlands",
             "components": {"street": "Damrak", "house_number": "1-5",
                            "locality": "Amsterdam", "postcode": "1012LG",
                            "country_code": "NL"},
             "location": {"lat": 52.3772801, "lon": 4.8976771}}},
  {"query": "Nowhere Street 999999 Zzz", "country": null, "ambiguous": true,
   "match": {"id": "p158899463", "type": "place",
             "text": "NOWHERE — Wooster Street 40, 10013 New York, USA"}}
]

Try it

Runs for real, from your browser, against wherever this page is hosted. Your key is never sent anywhere but the API itself, and it is not saved anywhere -- reload the page and it is gone.

Prefilled examples -- one click loads a worked query and runs it (Latin and native-script addresses, a typo, a radius search, a named place):

Stays in this tab's memory only -- never written into the page, never logged, gone on reload.

No session yet -- run an example, or click "New session".