---
title: "Geocode an address (versioned alias, JSON body)"
method: POST
path: "/v1/geocode"
tags: ["Versioned aliases (v1)"]
---

# Geocode an address (versioned alias, JSON body)

`POST /v1/geocode`

## Request

- Base URL: `https://api.acuris-geo.com`
- URL: `https://api.acuris-geo.com/v1/geocode`
- Auth: one of:
  - API key in header `X-Acuris-Key`
  - HTTP bearer
  - API key in header `X-API-Key`
  - API key in query parameter `key`
  - none

Versioned alias of `GET /geocode` — same handler, same response shape, same
billing. Accepts the geocode parameters as a JSON body instead of a query
string (scalar body fields are folded onto the query params; query-string
values win on collision). In addition to the fielded parameters, this alias
accepts a single-line free-text address as `q`, which is parsed into
components with the same parser the `/validate` path uses (explicit fielded
params take precedence over the parsed line). `q` also works on
`GET /geocode`; and `GET /v1/geocode` with the same query parameters as the
unversioned route is accepted too.

**Credits:** `geocode=1` Stripe-plan transaction per call. Combined AV+GEO pool since 2026-07-23: an address validation costs one transaction and includes coordinates at no extra cost; a standalone geocode also costs one transaction, from the same plan allowance. The trial pool
decrements **1** regardless. Anonymous callers consume one free slot per IP
(per-product daily free limits apply — check remaining via `/geocode-credits`;
anonymous access is rate-limited per IP).

## Request body

- object
  - `country` string, required
  - `q` string: Single-line free-text address, parsed into components.
  - `street` string
  - `hno` string: House number
  - `city` string
  - `town` string: Alternative to `city` for some countries
  - `state` string: State (USA) or province (Canada)
  - `postalcode` string: Postcode. The aliases `postcode`, `zip`, `zipcode`, `zip_code` and `postal_code` are also accepted; `postalcode` wins when several are sent.
  - `key` string: API key in the body (legacy; folded onto `?key=`). Header auth is preferred.
  - `db_version` string: Ignored. Reference DBs are configured at server startup via ref_dbs.json.

## Response `200`

Geocode result — a whitelist of geocoding-only fields. See `GET /geocode` for worked examples.

- ForwardGeocodeResponse: Response for `GET /geocode` and `POST /v1/geocode`. A strict whitelist of geocoding-only fields — every key that can appear is listed below, and no `/validate` AV field (`status`, `deliverable`, `corrections`, `match_components`, `match_details`, `match_score`, `match_type`, `confidence`, `confidence_level`, `input_corrected`, `provided_components`, `parsed`, `partial_match`, `house_number_not_found`, `hno_range_verified`, `enrichment`, or any legacy variant) can appear here. `lat`, `lng`, `accuracy_type`, `point_coordinate`, `standardized` and `formatted_address` are always present on a 200; the rest — including the billing tail (`balance`/`validation_credits`/`free_remaining`/ `key_class`/`free_daily_limit`) and the country-conditional fields (`attribution`, `uprn`, `digipin`, and for Portugal `reasons`, `nearby_house_numbers`, `approximate`, `resolved_level`, `resolved_name`) — are emitted only when applicable.
  - `lat` number, float, required
  - `lng` number, float, required
  - `accuracy_type` string, nullable, required: What the returned coordinate represents. `null` on `no_match`. **This is an OPEN set.** New tiers are added as reference data improves. Clients MUST pass unrecognized values through rather than throwing, and SHOULD branch on `point_coordinate` rather than on these literals. A **point coordinate** identifies one address: * `rooftop` — a point on the building itself. * `arrival_point` — the address's entrance, gate or curb-side access point. A point for *this* address, typically a few metres from the roof — deliberately not claimed as the building footprint. Everything else is derived rather than observed — a segment or an area, not an address: * `street_interpolated` — HNO estimated along a street segment polyline (SEG cascade) from the position of neighbouring house numbers. * `street_centroid` — street confirmed; exact HNO not located. **Singapore:** block numbers are not linear along a road and a letter-suffixed block (e.g. `700D`) is a separate building with its own postcode, so Singapore never returns `street_interpolated`. An unconfirmed block (or a street with no block) answers `street_centroid` at the street's centre, and `standardized.postcode` is left empty unless you supplied a postcode that exists on that street — a postcode names one building, and another building's postcode is never shown as yours. * `landmark_proximity` — India only. The address itself resolved no further than its postcode/locality, so the coordinate is that of a LANDMARK the caller named in the input ("opp. Swami Samarth Math"). It is a point on a *neighbouring* place, never the address, and it is only ever used when it out-ranks what would otherwise be returned. The landmark used is named back to you in `landmark_used` (`name`, `matched_from`, `scope`, `source`). * `postcode_centroid` — only postcode matched. * `locality_centroid` — only city/locality matched. * `admin_centroid` — only admin region (state/province) matched.
  - `point_coordinate` boolean, required: `true` when the coordinate identifies one address (`rooftop` or `arrival_point`), `false` for any derived/area tier. Branch on this rather than on `accuracy_type` literals — it stays correct when new tiers are added.
  - `standardized` object, required: The matched address, componentized. Only the components the match actually resolved are present — an unresolved component (e.g. no `state` for a country that does not use one) is simply absent from the object, never emitted as `null` or `""`.
    - `street` string
    - `house_number` string
    - `postcode` string
    - `city` string
    - `state` string
    - `country` string
    - `dependent_locality` string: Royal Mail PAF sub-element (GBR) / secondary locality line. Present only when the matched row carries one — e.g. the village between street and post town ("10 HAYDEN LANE / CHELTENHAM" would otherwise drop it, leaving two same-named streets in different villages indistinguishable).
    - `double_dependent_locality` string: Royal Mail PAF sub-element (GBR), finer than dependent_locality.
    - `dependent_street` string: Royal Mail PAF sub-element (GBR) — a secondary street name.
    - `street_type` string: Thoroughfare type carried in its own DB column rather than folded into `street` (e.g. Malaysia's UPU S42 type).
    - `suburb` string: NZ Post's line between street and post town (NZL). Omitted when it just repeats `city`.
    - `province` string: Province sigla for the mandatory Poste Italiane locality line (ITA).
  - `formatted_address` string, required: Printable multi-line address block, e.g. for a shipping label.
  - `arrival_point` object: Entrance / access coordinate of the matched premise. Present only when the matched row has one — house-number-level matches — and absent on street/locality fallbacks and when the served coordinate came from a POI. Never `null`; never derived from `lat`/`lng`.
    - `lat` number, float
    - `lng` number, float
  - `input_modified` string[]: Lowercase names of the components the engine changed from what the caller sent in a single-line `q=` input (e.g. `["street", "city"]`), deduped. States a fact about the input, not a verdict about the address — it is not a re-admission of the forbidden `corrections` / `input_corrected` AV fields. Present only when non-empty: no key at all when nothing was modified, never an empty array.
  - `attribution` string: GBR only.
  - `uprn` string: GBR only. Ordnance Survey Unique Property Reference Number, present when known.
  - `digipin` string: IND only. India Post DIGIPIN, a deterministic 10-character code derived purely from `lat`/`lng` (never from address existence — it says nothing about whether the address itself was verified). Present only when this deployment has DIGIPIN enabled; not guaranteed on every India response even from a deployment that has it enabled (omitted if no coordinate was resolved, or the encoder found none).
  - `reasons` FailureReason[]: PRT only, optional. Present when the point is not a confirmed house: what could not be pinned down (e.g. `house_number_unconfirmed` + `house_number_not_in_reference`, `below_verification_threshold` + `house_number_missing`, or `street_missing` / `fell_back_to_locality` on an approximate point). Same codes as `/validate`.
  - `nearby_house_numbers` NearbyHouseNumbers: **Portugal (`country: prt`) only.** Present on a 200 answer when the street was matched but the house number you sent is not in the reference data — the answer is then either street-level (`street_centroid`) or `street_interpolated` at a point between the nearest same-parity house numbers (`pin_between`). It lists which numbers of that street ARE held, on the same side of the street (odd and even numbers usually face each other). Informational only: it never changes the coordinate, tier, confidence or `deliverable`. Sent with reason `house_number_not_in_reference`. Pass unrecognized fields through.
    - `requested` string: The house number as you sent it.
    - `parity` string
    - `before` NearbyHouseNumber
      - `hno` string
      - `lat` number, float, nullable
      - `lng` number, float, nullable
      - `accuracy` string, nullable: The coordinate tier of that house number (e.g. `rooftop`), `null` without a coordinate.
      - `has_coordinates` boolean: `false` when the number exists in the reference data without its own coordinate (`lat`/`lng`/`accuracy` are then `null`).
    - `after` NearbyHouseNumber
      - `hno` string
      - `lat` number, float, nullable
      - `lng` number, float, nullable
      - `accuracy` string, nullable: The coordinate tier of that house number (e.g. `rooftop`), `null` without a coordinate.
      - `has_coordinates` boolean: `false` when the number exists in the reference data without its own coordinate (`lat`/`lng`/`accuracy` are then `null`).
    - `pin_between` string[], nullable: The two held numbers the interpolated point was placed between (both may be the same number, e.g. `41` for a requested `41B`); `null` when the answer is street-level rather than interpolated.
    - `same_parity_available` string[]: Up to 40 held numbers on the same side nearest to the requested one, in ascending order (letter suffixes such as `2A` included).
    - `total_doors` integer: Number of distinct house numbers held for the street.
    - `coordinated_doors` integer: How many of those carry their own coordinate.
  - `street_postcodes` object[]: Netherlands only, optional. Present only on STREET-LEVEL answers (`accuracy_type` `street_centroid`, `street_segment_centroid` or `street_interpolated`): the postcodes the matched street covers, each with its house-number range. Sorted by `house_number_from`, then `postcode`; at most 50 entries (see `street_postcodes_total` and `street_postcodes_truncated`). Ranges count numeric house numbers only; addresses whose number carries letters are not counted. Absent on rooftop and other house-number-level answers.
    - `postcode` string
    - `house_number_from` integer
    - `house_number_to` integer
    - `parity` 'odd' | 'even' | 'both': Which side(s) of the street the postcode covers within that range.
  - `street_postcodes_total` integer: With `street_postcodes`, how many postcodes the street covers (not capped).
  - `street_postcodes_truncated` boolean: With `street_postcodes`, `true` when the street covers more postcodes than are listed.
  - `postcode_range` object: Netherlands only, optional. Sent with `street_postcodes` when the street covers MORE THAN ONE postcode: the lowest and highest postcode over the whole street (an outer bound, not a continuous run; `street_postcodes` lists the exact ones). In that case `standardized.postcode` carries `text` instead of a single postcode and `standardized.formatted_address` prints it. Check for this field before treating `standardized.postcode` as one postcode.
    - `from` string
    - `to` string
    - `text` string
  - `approximate` boolean: PRT only, optional. `true` when the point is a town / area centre standing in for the address — a city- or neighbourhood-only request (`street_missing`) or a street that could not be placed (`fell_back_to_locality`). `accuracy_type` is then `locality_centroid` or `admin_centroid`.
  - `resolved_level` string: **Portugal (`country: prt`) only.** Which kind of place an approximate point (`approximate: true`) stands for. Open set — pass unknown values through. * `locality` — a locality (village / town). * `postal_town` — the town name of the postal address. * `municipality` — a municipality (concelho). * `district` — a district. * `lisbon_parish` — a Lisbon parish or neighbourhood name; the point is Lisbon's.
  - `resolved_name` string: PRT only, with `approximate`. The place's name as spelled in the reference data.
  - `balance` integer: Remaining geocode transactions after a keyed, non-free-tier call.
  - `validation_credits` integer
  - `free_remaining` integer: Free calls left today, for anonymous and non-`free_av_geo` free-tier callers.
  - `key_class` string: Present only for verified `free_av_geo` keys — `"free_av_geo"`. See `/check-key`.
  - `free_daily_limit` integer: Present only for verified `free_av_geo` keys. Always `1000` (the shared daily allowance).

## Other responses

- `400`: Missing/unsupported `country`, or neither `q` nor any fielded component supplied Portugal: also `Missing required field: street` for a city-only request whose place name is not found.
- `403`: Invalid key, no transaction balance, or anonymous geocode limit reached. Key-level 403s: `{"error": "key_blocked", "message"}` when the API key has been blocked (the `message` says why), and `{"error": "ip_not_allowed", "message", "ip"}` when the key is restricted to approved source IP addresses and this request came from another one (`ip` is the address we saw). Source-IP restriction is optional and set up per key on request via support.
- `404`: No match. Body is `{"error": "no_match", "message": "..."}` — never the `/validate` match-quality contract (no `match_type`, `match_score`, `match_components`, `input_corrected`). Portugal: may also carry `reasons`, `did_you_mean` and `did_you_mean_truncated` (see `ForwardGeocodeErrorBody`).
- `422`: Input could not be interpreted. Same `{"error": "no_match", "message": "..."}` shape as 404.
- `429`: Too many requests (anonymous limit or WAF/limiter)
- `500`: Internal error during geocoding. Body: `{"error": "Internal error"}`.
- `503`: Reference database not available, or another engine-level failure.
- `504`: Geocode lookup timed out.

---

[API](https://skmtc.dev/acuris-geo/apis/acuris-geo-api.md) · [All operations](https://skmtc.dev/acuris-geo/apis/acuris-geo-api/llms.txt) · [OpenAPI document](https://skmtc.dev/acuris-geo/apis/acuris-geo-api/revisions/8547b31dd96a?raw)
