Skip to the content.

API reference

Authoritative request/response shapes for the core Acuris endpoints (validate / geocode / reverse / suggest, plus UK postcode lookup and email validation), the client configuration surface, and the error hierarchy. Derived from the @acuris-geo/av-sdk TypeScript types — those are the source of truth if anything here drifts.

Authentication

Single header. Send your API key in X-Acuris-Key:

POST /validate HTTP/1.1
Host: api.acuris-geo.com
X-Acuris-Key: <ACURIS_API_KEY>
Content-Type: application/json
Accept: application/json

The SDK sets this automatically. If you are calling the API by hand (curl, Postman, generated client from a third-party tool), use this header — not Authorization: Bearer.

Getting a key

Two self-service paths:

Endpoint Best for Caps
POST /dev-key AI-skill / SDK evaluation 100 validations + 100 geocodes, 7 days; 1/email/day.
POST /register (web form at /register) Full trial, paid plan onboarding 1000 validations + 1000 geocodes, 28 days.

POST /dev-key is one step and returns the key in the response body. "accept_terms": true states that the user agrees to the Acuris Geo Terms of Service and the UK PAF Evaluation Terms. An agent must show the user both links and get their confirmation before sending it. When acceptance is enforced, a request without it returns 400 {"error": "terms_acceptance_required", "terms_url": ..., "uk_evaluation_terms_url": ...}.

curl -X POST https://api.acuris-geo.com/dev-key \
  -H 'Content-Type: application/json' \
  -d '{"email":"you@example.com","accept_terms":true}'

Response:

{
  "api_key":             "<token>",
  "validation_credits":  100,
  "geocode_credits":     100,
  "expires_at":          "2026-... UTC",
  "tier":                "dev",
  "info_url":            "https://acuris-geo.com/acuris-pricing/",
  "next_steps":          "Set ACURIS_API_KEY=<api_key> ..."
}

The dev key authenticates every endpoint (/validate, /geocode, /reverse, /suggest). It’s not a special “demo-only” tier — the calls execute against the same live cascade as paid traffic.

Note on the literal value test. The string "test" is not a universal evaluation key. It works on /suggest for legacy marketing-site widgets but is rejected by /validate, /geocode, and /reverse. Use POST /dev-key for evaluation instead — it takes 5 seconds and gives you a real key.

Base URL

https://api.acuris-geo.com. There is no separate sandbox host — the test key gates evaluation traffic on the live API. If you are running the Acuris on-prem build, override baseUrl on the client.

Client configuration

import { AcurisClient } from "@acuris-geo/av-sdk";

const client = new AcurisClient({
  apiKey:     process.env.ACURIS_API_KEY,       // required (or env)
  baseUrl:    "https://api.acuris-geo.com",     // default
  timeoutMs:  5000,                              // per-request, default
  maxRetries: 3,                                 // 5xx/429/net/timeout
  fetch:      globalThis.fetch,                  // override for tests
  userAgent:  "myapp/1.0",                       // appended after acuris-av-sdk/<ver>
});

A long-lived AcurisClient instance is the recommended pattern — it keeps keep-alive sockets warm and avoids re-reading env vars per call.

POST /validate — validate an address

SDK: validateAddress(client, input, options?).

Request body:

{
  "country": "deu",
  "input": {
    "street":       "Friedrichstraße",
    "house_number": "43",
    "city":         "Berlin",
    "locality":     null,
    "state":        null,
    "postcode":     "10117"
  }
}

input may also be a single-line string:

{ "country": "usa", "input": "100 Main St, San Francisco CA 94105" }

Response (ValidationResult):

{
  "accuracy_type":  "rooftop",
  "confidence":     0.97,
  "match_type":     "exact",
  "match_score":    0.99,
  "match_components": {
    "city": true, "house_number": true, "state": true,
    "street": true, "zip": true
  },
  "input_corrected": false,
  "standardized": {
    "country":      "deu",
    "city":         "Berlin",
    "state":        "Berlin",
    "postcode":     "10117",
    "street":       "Friedrichstraße",
    "house_number": "43",
    "formatted_address": "Friedrichstraße 43\n10117 Berlin\nGermany"
  },
  "parsed": { "...echo of how Acuris parsed the raw input..." },
  "corrections": [],
  "lat": 52.5074, "lng": 13.3899,
  "house_number_not_found": false,
  "status": "V2"
}

Field meanings:

Field What it tells you
accuracy_type Precision bucket (rooftop, parcel, street_interpolated, street_center, postcode, locality_centroid, centroid, country, …). Open enum.
confidence 0..1. Higher = more confident the match represents the user’s intent.
match_type exact / interpolated / partial / no_match / rooftop.
match_components Per-field booleans — which components matched.
input_corrected True if Acuris altered the input to produce a match.
standardized Canonical form. Use formatted_address for display.
corrections Human-readable list of what Acuris changed, if anything.
lat / lng Forward-geocoded coords when available.
house_number_not_found True if house-level coords weren’t found and a coarser tier was returned.
standardized.county / standardized.udprn / standardized.uprn UK only (country: "gbr"). Traditional county + Royal Mail UDPRN + Ordnance Survey UPRN, from Royal Mail PAF / OS Open UPRN.

GET /geocode — forward geocode (fielded only)

SDK: geocodeAddress(client, input, options?).

/geocode requires structured fields. The SDK auto-detects: if you pass a string it falls back to /validate (which handles freeform) and maps the response to GeocodingResult.

Request (query params):

GET /geocode?country=deu&street=Friedrichstra%C3%9Fe&hno=43
            &city=Berlin&postalcode=10117

Field mapping from SDK input → query param:

SDK input field Query param
street street
house_number hno
city city
locality town
state state
postcode postalcode

Response (GeocodingResult): same shape as standardized plus accuracy_type, match_type, match_score, match_components, lat, lng, formatted_address, house_number_not_found.

GET /reverse — reverse geocode

SDK: reverseGeocode(client, { lat, lng }, options).

Request:

GET /reverse?country=usa&lat=37.7749&lng=-122.4194&radius_m=100&limit=3

Response (ReverseGeocodingResult):

{
  "hits": [
    {
      "accuracy_type": "rooftop",
      "match_type":    "exact",
      "match_score":   0.98,
      "distance_m":    14,
      "lat":           37.7748,
      "lng":           -122.4195,
      "country":       "usa",
      "city":          "San Francisco",
      "state":         "CA",
      "zip":           "94102",
      "street":        "Market St",
      "hno":           "1"
    }
  ],
  "query": { "lat": 37.7749, "lng": -122.4194, "radius_m": 100 }
}

country on the options is required — the API has no input from which to infer it. lat must be in [-90, 90]; lng in [-180, 180].

GET /suggest — autocomplete prefix → suggestions

SDK: suggestAddress(client, q, options).

Request:

GET /suggest?country=deu&q=Friedrichstr&limit=5

Optional state (USA / CAN / AUS / …) biases results. limit defaults to 10, server caps at 50.

Response:

{
  "suggestions": [
    {
      "country": "deu",
      "city":    "Berlin",
      "postcode": "10117",
      "street":  "Friedrichstraße",
      "house_number": "43",
      "lat": 52.5074, "lng": 13.3899,
      "formatted_address": "Friedrichstraße 43\n10117 Berlin\nGermany"
    }
  ]
}

The SDK returns the bare SuggestionHit[] array (it unwraps suggestions). Empty arrays come back as [] (not null).

POST /postcode-lookup — every address at a UK postcode (Royal Mail PAF)

UK-only. Returns every delivery point at a postcode, backed by the official Royal Mail Postcode Address File. Each address carries the Royal Mail udprn, the Ordnance Survey uprn, the traditional county, and rooftop coordinates when available. Charges 1 validation credit per call, regardless of the number of addresses returned.

POST /postcode-lookup
{ "postcode": "SW1A 2AA", "country": "gbr" }
{
  "postcode": "SW1A 2AA",
  "country": "gbr",
  "count": 2,
  "addresses": [
    {
      "street": "Downing Street", "house_number": "10",
      "building_name": "", "organisation_name": "Prime Minister & First Lord Of The Treasury",
      "city": "London", "postcode": "SW1A 2AA", "county": "Greater London",
      "udprn": "23747771", "uprn": "100023336956",
      "lat": 51.50344, "lng": -0.12770, "accuracy_type": "rooftop"
    }
  ]
}

Results are capped (default 100); "truncated": true is set when the cap is hit. A per-IP velocity cap also applies (the dataset is licensed). Returns 404 when the postcode isn’t found, 403 on insufficient credits.

POST /email-validate — email deliverability

Checks format, DNS MX, and disposable / role / free-provider flags, plus a did_you_mean typo suggestion. Email verifications draw on a separate credit pool from address validation and geocoding (1 email credit per call).

Bulk / async. POST /email-validate/batch validates up to 1,000 emails synchronously (charged atomically). For larger lists — or webhook delivery — use the async POST /email-validate/jobs (up to 10,000 per job): it returns a job_id (HTTP 202); poll GET /email-validate/jobs/{job_id} for status (queued / processing / done / error) and, once done, the per-email results — or pass webhook_url for a completion callback. Async jobs require an API key and run on the main box only.

POST /email-validate
{ "email": "user@example.com" }
{
  "email": "user@example.com",
  "deliverable": "deliverable",
  "score": 0.95,
  "format_valid": true,
  "domain_has_mx": true,
  "is_disposable": false,
  "is_role": false,
  "is_free": false,
  "did_you_mean": null,
  "mx_record": "aspmx.l.google.com",
  "reasons": []
}

deliverable is one of deliverable / risky / undeliverable / unknown.

Errors

The SDK throws a typed hierarchy:

import {
  AcurisError,            // base
  AcurisAuthError,        // 401, 403
  AcurisValidationError,  // 400, 422
  AcurisNotFoundError,    // 404
  AcurisRateLimitError,   // 429 (carries `retryAfterSeconds`)
  AcurisServerError,      // 5xx
  AcurisTimeoutError,     // client-side timeout
  AcurisNetworkError,     // fetch failed before producing a response
  isTransientStatus,      // helper: status → boolean
} from "@acuris-geo/av-sdk";

Every subclass carries status?, body?, endpoint?, and cause?.

Recommended handling:

try {
  const result = await validateAddress(client, input, { country: "deu" });
  // ...
} catch (err) {
  if (err instanceof AcurisAuthError)       throw new Error("Acuris key misconfigured");
  if (err instanceof AcurisValidationError) return { ok: false, kind: "bad_input", err };
  if (err instanceof AcurisRateLimitError)  return { ok: false, kind: "rate_limit", retryAfter: err.retryAfterSeconds };
  if (err instanceof AcurisNotFoundError)   return { ok: false, kind: "no_match" };
  // AcurisServerError / Timeout / NetworkError — already retried by the SDK
  throw err;
}

Retry semantics

The SDK retries the following automatically with exponential backoff (default maxRetries: 3):

Anything else propagates immediately. Don’t add a second retry loop on top — you’ll multiply the budget and confuse the rate-limiter.

Tuning timeouts and the retry budget

The default timeoutMs: 5000 is right for most server-side use. Two situations want different settings:

Per-call overrides also work:

await validateAddress(client, addr, {
  country: "deu",
  timeoutMs: 15_000,
  maxRetries: 5,
});

Cancelling

Every SDK call accepts an AbortSignal:

const ac = new AbortController();
const p  = validateAddress(client, input, { country: "deu", signal: ac.signal });
// ...
ac.abort(); // p rejects with AbortError

Useful inside React useEffect cleanups to drop stale autocomplete responses when the user types more characters.

TypeScript types (full surface)

export type CountryCode = string; // ISO-3 lowercase

export type AddressInput = string | FieldedAddressInput;

export interface FieldedAddressInput {
  street?: string;
  house_number?: string;
  city?: string;
  locality?: string;     // sub-locality / district
  state?: string;
  postcode?: string;
  country?: CountryCode;
}

export type AccuracyType =
  | "rooftop" | "parcel" | "street_interpolated" | "street_center"
  | "postcode" | "postcode_center" | "locality" | "locality_centroid"
  | "centroid" | "country"
  | (string & {});                // open enum

export interface ValidationResult { /* see /validate above */ }
export interface GeocodingResult   { /* see /geocode above */ }
export interface ReverseGeocodingResult { hits: ReverseGeocodingHit[]; query: { lat: number; lng: number; radius_m: number } }
export interface SuggestionHit     { /* see /suggest above */ }

Full type definitions live in packages/acuris-av-sdk/src/types.ts in the centra-connector repo.