> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mireye.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# POST /v1/geocode

> Resolve a US street address to a coordinate, with the quality of that coordinate.

## When to use this

<Note>
  You usually do not need this endpoint. [`/v1/fetch`](/api-reference/fetch) and
  [`/v1/ask`](/api-reference/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.
</Note>

Every other endpoint takes `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. |

The parser is Geocodio's, so ordinary formatting variation is fine —
`Pkwy`/`Parkway`, `N`/`North`, `NY`/`N.Y.`, extra whitespace, mixed case.
What it cannot do is guess a locality: a bare `"706 Ocean Pkwy"` with no
city, state, or ZIP is ambiguous nationwide and comes back
`404 address_too_coarse`.

## Example: a rooftop match

```bash theme={null}
curl -s https://api.mireye.com/v1/geocode \
  -H "Authorization: Bearer $MIREYE_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"address": "706 Ocean Pkwy, Brooklyn, NY 11230"}' | jq
```

```json theme={null}
{
  "lat": 40.630973,
  "lng": -73.97228,
  "accuracy": 1.0,
  "accuracy_type": "rooftop",
  "match_type": "building_centroid",
  "normalized_address": "706 Ocean Pkwy, Brooklyn, NY 11230",
  "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:

```bash theme={null}
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
```

```json theme={null}
{
  "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"
}
```

The result is honest — `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_address` and 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 returns **`404 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.

```bash theme={null}
curl -s -i https://api.mireye.com/v1/geocode \
  -H "Authorization: Bearer $MIREYE_API_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"address": "Brooklyn, NY"}'
```

```http theme={null}
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
  }
}
```

Retrying will not help — the upstream's answer is stable. Ask the user for
a street number.

## 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](/api-reference/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
```

Different street, different ZIP, different town — returned at near-perfect
confidence. **Every guard on this page passes these.** They sit inside the US
envelope, above the similarity floor, and the country component says `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         | `geocodio` normally; `census` when the primary timed out (see below).                                                                                                                                                                                                                                                                                                                                                                                          |
| `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. |

Every retryable failure carries a `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](/api-reference/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-34` Queens house numbers, `# 4` units, `123 1/2`
  half-addresses) — so
  `706 Ocean Pkwy` and ` 706 OCEAN PKWY` are one entry.
  It deliberately does **not** treat `Parkway` and `Pkwy` as 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 Blvd`
  would collapse into `N 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/fetch` or `/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 resolved
`lat`/`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, `geocode` block) 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.

Nothing is kept beyond that.

The two are linked, and the API enforces both ends itself. It **removes expired
address fields from its own records** on a rolling check — the removal does not
depend on any scheduled job staying configured — and if it ever cannot remove
them, it **stops recording addresses** until it can. We keep the address because
it expires; the component that accepts it is the component that deletes it.

Two things we deliberately do not do. We do not store a hash of your address
next to it — over an address space this small a hash is a lookup key, not
anonymity, so it would add exposure while implying the opposite. And the
address is held in one structured field rather than copied into logs or stored
upstream URLs.

If your use case cannot accommodate this, geocode client-side and call
`/v1/fetch` or `/v1/ask` with `lat`/`lng` instead — a coordinate request stores
no address, because none was sent.
