Resolve a US street address to a coordinate
curl --request POST \
--url https://api.example.com/v1/geocode \
--header 'Content-Type: application/json' \
--data '
{
"address": "<string>"
}
'import requests
url = "https://api.example.com/v1/geocode"
payload = { "address": "<string>" }
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({address: '<string>'})
};
fetch('https://api.example.com/v1/geocode', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/geocode",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'address' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/geocode"
payload := strings.NewReader("{\n \"address\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/geocode")
.header("Content-Type", "application/json")
.body("{\n \"address\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/geocode")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"address\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Resolve
POST /v1/geocode
Resolve a US street address to a coordinate, with the quality of that coordinate.
POST
/
v1
/
geocode
Resolve a US street address to a coordinate
curl --request POST \
--url https://api.example.com/v1/geocode \
--header 'Content-Type: application/json' \
--data '
{
"address": "<string>"
}
'import requests
url = "https://api.example.com/v1/geocode"
payload = { "address": "<string>" }
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({address: '<string>'})
};
fetch('https://api.example.com/v1/geocode', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/geocode",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'address' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/geocode"
payload := strings.NewReader("{\n \"address\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/v1/geocode")
.header("Content-Type", "application/json")
.body("{\n \"address\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/geocode")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"address\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}When to use this
You usually do not need this endpoint.
/v1/fetch and
/v1/ask both accept an address directly and echo the
same quality information back in a geocode block. Reach for /v1/geocode
when you want the coordinate on its own — to inspect it before spending a
fetch, to cache it yourself, or to reuse it across many calls.lat/lng. This one turns a street address into
that coordinate, and tells you how it was derived.
Geocoding is lossy — a coordinate can land on the wrong parcel — so wherever you
use it, the quality signal travels with the answer. As a standalone call you see
accuracy_type before doing anything else; via address on /v1/fetch you get
the same fields in the response’s geocode block.
Request shape
| Field | Type | Required | Notes |
|---|---|---|---|
address | string | yes | 1–256 characters. A full US street address. Over 256 is rejected, never truncated. |
Pkwy/Parkway, N/North, NY/N.Y., extra whitespace, mixed case.
What it cannot do is guess a locality: a bare "350 5th Ave" with no
city, state, or ZIP is ambiguous nationwide and comes back
404 address_too_coarse.
Example: a rooftop match
curl -s https://api.mireye.com/v1/geocode \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H 'content-type: application/json' \
-d '{"address": "350 5th Ave, New York, NY 10118"}' | jq
{
"lat": 40.748377,
"lng": -73.984854,
"accuracy": 1.0,
"accuracy_type": "rooftop",
"match_type": "building_centroid",
"normalized_address": "350 5th Ave, New York, NY 10118",
"provider": "geocodio"
}
Read accuracy_type before you trust the coordinate
This is the field that matters. It says how the coordinate was
derived, and the difference is the difference between the right property
and a neighbour’s.
accuracy_type | Grade | What it means |
|---|---|---|
rooftop | parcel | Matched a known structure. Lands on the parcel. |
nearest_rooftop_match | parcel | Matched the nearest known structure on the block. |
point | street | A known point location, not a rooftop. |
range_interpolation | street | Estimated along a street centerline from the house-number range. |
intersection | street | The junction of two streets. |
street_center | street | The midpoint of the street. |
place county state | centroid | Rejected — see below. |
range_interpolation is the one to watch. It assumes house numbers are
evenly spaced along the block, which holds in a dense grid and breaks in
the countryside. Measured against authoritative county parcel polygons in
North Carolina: every rooftop result fell inside its own parcel with a
median error of 0 m, while interpolated results fell outside, one of them
by 1.1 km. In rural areas the 95th-percentile error is about 2,872 m —
several properties over.
Here is what that looks like. Note that the request said Kyle, TX and the
answer is in San Marcos:
curl -s https://api.mireye.com/v1/geocode \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H 'content-type: application/json' \
-d '{"address": "2470 Hunter Rd, Kyle, TX 78640"}' | jq
{
"lat": 29.859,
"lng": -97.967533,
"accuracy": 0.93,
"accuracy_type": "range_interpolation",
"match_type": null,
"normalized_address": "2470 Hunter Rd, San Marcos, TX 78666",
"provider": "geocodio"
}
accuracy_type and normalized_address both tell
you what happened — but a caller that reads only lat/lng would
silently underwrite the wrong site. A reasonable rule:
rooftop/nearest_rooftop_match— safe for parcel-level work.- anything else — treat as approximate. Show the user
normalized_addressand let them confirm before you act on it.
Vague addresses are rejected, not guessed
An address the geocoder can only place at a ZIP, city, county, or state centroid returns404 address_too_coarse instead of a coordinate.
This is worth stating plainly because the upstream behaves differently:
given a nonexistent address, Geocodio does not report “no match” — it
answers 200 with the nearest city centroid. Passing that through would
hand you a confident-looking coordinate for an arbitrary property near a
town centre, and every parcel-clipped field downstream would describe the
wrong land, with citations.
curl -s -i https://api.mireye.com/v1/geocode \
-H "Authorization: Bearer $MIREYE_API_TOKEN" \
-H 'content-type: application/json' \
-d '{"address": "Manhattan, NY"}'
HTTP/1.1 404 Not Found
{
"detail": {
"error": "address_too_coarse",
"message": "resolved only to place, which is not specific enough to identify a property",
"retryable": false
}
}
Two more ways a request is refused
Beyond the centroid floor above, a result is rejected when it is precise but about the wrong place. These are different failures and need different gates —accuracy_type says how precisely a match was placed, accuracy says
whether the right thing was matched at all.
Low confidence. Below 0.8 similarity (the provider’s own caution
threshold) the match is a guess, so it is refused rather than returned.
"1 Rue de Rivoli, Paris, France" used to come back as a Virginia coordinate at
0.65. Override with MIREYE_GEOCODE_MIN_SIMILARITY if you have a reason to.
Outside the US. We do not ask the provider to force a US match. Doing so
does not filter to US addresses — it asks for the best US thing available,
which is how "10 Downing Street, London, UK" became a rooftop-grade
coordinate in Charleston, West Virginia. Unforced, that query correctly returns
nothing, and a result that does come back non-US is refused.
Both surface as the same 404 codes documented under
Errors.
Known limitation: US territories
Coverage in Puerto Rico, Guam, the US Virgin Islands, American Samoa and the Northern Marianas is thin — and unlike everywhere else, the provider’s confidence score does not reflect that. Measured 2026-07-26:asked: 1 Calle Fortaleza, San Juan, PR 00901
got: 1 Calle E, San Juan, PR 00926 accuracy 0.99
asked: 1 Marine Corps Dr, Hagatna, GU 96910
got: 1 Marine Corps Dr, Barrigada, GU 96913 accuracy 0.94
US.
We have not added a heuristic for it, deliberately. Every rule that catches
these also rejects legitimate mainland addresses: a real rural Texas address
that correctly resolves to a neighbouring town shares fewer tokens with its
match than the wrong Puerto Rico one does with its. Trading real addresses for
territory ones is the worse deal.
So this check is yours, and it is the ordinary one: compare
normalized_address against what your user typed. For territory addresses,
treat that as required rather than advisory.
It is not uniform. A San Juan address on a well-covered street resolved
correctly at 0.91, and the US Virgin Islands, American Samoa and the Northern
Marianas refuse cleanly rather than guessing. Puerto Rico and Guam are where a
wrong answer can look right.
Response shape
| Field | Type | Notes |
|---|---|---|
lat | number | WGS84 latitude. |
lng | number | WGS84 longitude. |
accuracy | number or null | Provider similarity score, 0–1 — how confident the provider is that it matched the right address, which is a different question from accuracy_type. Anything below 0.8 is rejected, not returned (see below). null from the fallback provider, which publishes no score; absent is not treated as zero. |
accuracy_type | string | How the coordinate was derived. See the table above. |
match_type | string or null | Extra provider detail, e.g. building_centroid. |
normalized_address | string or null | The address the provider actually matched. Compare it against what your user typed — this is how you catch a wrong-town match. |
provider | string | Which upstream answered, as a tier rather than a vendor name: commercial normally, census when the primary timed out and the free lower-precision fallback took over. other is reserved for an upstream we have not characterised — treat it as unknown precision. |
source | string or null | The authority the coordinate came from — e.g. City of New York, NC Geographic Information Coordinating Council, TIGER/Line® from the US Census Bureau. Distinct from provider, which is who we asked. A second, independent quality signal: a municipal parcel layer and a federal street-centerline file are not equally good, and the difference does not always show up in accuracy_type. null on results cached before this field shipped. |
The fallback, and why provider matters
If the primary provider exceeds its 3 s deadline, we fall back to the free
US Census geocoder rather than failing the request. That fallback fires on
timeout only — never on a missing key, an auth failure, or a no-match,
because those must surface rather than be papered over with a
lower-precision answer.
The fallback is a genuine degradation, not an equivalent. In the same
North Carolina measurement it landed inside the correct parcel zero
times out of nine and missed 6 of 15 addresses entirely. It always reports
accuracy_type: "range_interpolation", and provider: "census" is your
signal that you are on the degraded path.
Errors
| Error code | HTTP | Retryable | Meaning |
|---|---|---|---|
address_not_found | 404 | no | The provider processed the query and has no match. |
address_form_unsupported | 422 | no | A PO box, carrier route (RR/HC), military APO/FPO, or general delivery. These route mail through a facility and describe no parcel, so no coordinate exists. Detected from the input, so it never costs a lookup. |
address_too_coarse | 404 | no | Resolved only to a ZIP/city/county/state centroid. |
geocode_busy | 429 | yes | Per-worker overload gate. Retry-After: 2. |
geocode_upstream_error | 502 | yes | Provider 5xx / connection blip. Retry-After: 2. |
geocode_timeout | 504 | yes | Both the primary and the fallback exceeded their deadlines. Retry-After: 5. |
geocode_unconfigured | 503 | no | The service is missing its provider credential. An operator problem, not a bad request. |
geocode_forbidden | 503 | yes | Credential recognized but the request was refused — a usage/spend limit or a missing plan entitlement. Not a bad key. The provider’s own message is passed through. Retry-After: 3600, since the usage cap resets daily. |
geocode_budget_exhausted | 503 | yes | Our own fleet-wide monthly geocode budget is spent. Raised before any billable lookup, so nothing was charged. Distinct from geocode_forbidden: that is the provider refusing us, this is us refusing ourselves. The counter rolls at the UTC month boundary, so Retry-After: 3600 means “much later”, not “in an hour it will definitely work”. |
Retry-After header; non-retryable ones
deliberately do not. A 1–256 character violation is a FastAPI 422, as
elsewhere in the API — see Errors.
Limits
- One address per request. No batching in V1; loop client-side.
- 256 characters maximum, rejected rather than truncated.
- Successful results are cached for 30 days; “no such address” for 24 hours.
A repeat lookup of the same address within the window is free and instant,
and the cache expires on the same 30-day clock as everything else we keep —
this page used to say “a year” here while promising 30 days two sections
down, and both could not be true. The cache key is normalized for
form only — case, spacing, accents and punctuation, except
-,#and/, which carry meaning (12-34Queens house numbers,# 4units,123 1/2half-addresses) — so350 5th Aveand350 5TH AVEare one entry. It deliberately does not treatParkwayandPkwyas the same word: doing that safely requires knowing which token is the street suffix, and guessing merges real streets whose name is a suffix word (Northern Blvdwould collapse intoN Blvd). Two spellings of one address therefore cost two lookups. That is the intended trade — a duplicate lookup is cheap, a merged cache entry would hand you a different property’s coordinate. A negative is deliberately short-lived: an address the provider has not ingested yet starts working the moment it does, and a month-long 404 would outlast the truth by months. - Worst case 5.5 s (3 s primary + 2.5 s fallback). Set your client timeout above that.
- US only. Feed the coordinate to
/v1/fetchor/v1/ask, both of which enforce the US envelope.
What we keep
The address you send is recorded, alongside the coordinate it resolved to and the accuracy grade that produced it. We keep it so a result can be checked later. If you tell us a lookup returned the wrong data for one of your addresses, the record of what you actually sent is the only thing that can settle it — with just a coordinate and a result, there is no way to tell a bad match from a typo. Concretely, per request we store the address as you typed it, the resolvedlat/lng, and the geocode block including accuracy_type and provider.
Retention is 30 days, everywhere the address lives. Two stores hold it and
both expire on the same clock:
- The audit record (address as typed, coordinate,
geocodeblock) sits in monthly partitions; a scheduled job drops each one once its whole month is past the cutoff, so a record lives 30 to about 60 days depending on when in the month it was created. - The result cache (the matched address and coordinate, keyed by a digest of your input, kept so a repeat lookup does not re-bill) expires at 30 days via the store’s own TTL — no job involved, so it cannot quietly stop.
/v1/fetch or /v1/ask with lat/lng instead — a coordinate request stores
no address, because none was sent.Body
application/json
Required string length:
1 - 256Response
Successful Response