Skip to the content.

Validate on form submit

After the user picks a suggestion (or types the whole address by hand), call /validate to confirm and lock in the canonical form before persisting. This catches typos, normalizes capitalization, and gives you accuracy_type + confidence to decide what to do with low-quality matches.

Backend route

// app/api/acuris/validate/route.ts  (Next.js App Router)
import {
  AcurisClient,
  validateAddress,
  AcurisAuthError,
  AcurisValidationError,
  AcurisRateLimitError,
  AcurisNotFoundError,
} from "@acuris-geo/av-sdk";

const client = new AcurisClient({ apiKey: process.env.ACURIS_API_KEY });

export async function POST(req: Request) {
  const { country, input } = await req.json();

  try {
    const result = await validateAddress(client, input, { country });
    return Response.json(result);
  } catch (err) {
    if (err instanceof AcurisAuthError)       return Response.json({ error: "config" },     { status: 500 });
    if (err instanceof AcurisValidationError) return Response.json({ error: "bad_input" },  { status: 400 });
    if (err instanceof AcurisNotFoundError)   return Response.json({ error: "no_match" },   { status: 404 });
    if (err instanceof AcurisRateLimitError)  return Response.json({ error: "rate_limit" }, { status: 429, headers: err.retryAfterSeconds ? { "Retry-After": String(err.retryAfterSeconds) } : undefined });
    return Response.json({ error: "upstream" }, { status: 502 });
  }
}

Frontend: deciding what to do with the result

accuracy_type and confidence together drive a three-way decision:

interface ValidationResult {
  accuracy_type: string | null;
  confidence: number;
  input_corrected: boolean;
  standardized?: { formatted_address?: string };
  corrections?: string[];
}

function classifyAddress(r: ValidationResult): "accept" | "confirm" | "reject" {
  if (!r.accuracy_type || r.confidence < 0.4)         return "reject";
  if (r.input_corrected || r.confidence < 0.8)        return "confirm";
  if (["rooftop", "parcel", "exact"].includes(r.accuracy_type))
                                                       return "accept";
  if (r.accuracy_type.startsWith("street"))           return "confirm";
  return "confirm";
}

Drop-in React component

If you want the decision logic above pre-wired:

import { AcurisAddressValidator, AcurisAddressInput } from "@acuris-geo/centra-checkout";

const ENDPOINTS = { validate: "/api/acuris/validate", suggest: "/api/acuris/suggest" };

export default function CheckoutAddress() {
  const [value, setValue] = useState("");
  return (
    <AcurisAddressValidator
      endpoints={ENDPOINTS}
      country="deu"
      address={value}
      trigger="submit"
    >
      {({ status, result, formProps }) => (
        <form {...formProps}>
          <AcurisAddressInput
            endpoints={ENDPOINTS} country="deu"
            value={value} onChange={setValue}
          />
          <button type="submit">Continue</button>
          {status === "ok"      && <p>{result?.standardized?.formatted_address}</p>}
          {status === "low_confidence" && (
            <p>Did you mean: {result?.standardized?.formatted_address}?</p>
          )}
          {status === "no_match" && <p>We couldn't verify this address.</p>}
        </form>
      )}
    </AcurisAddressValidator>
  );
}

trigger="submit" runs validation on form-submit. trigger="change" runs it as the user types, debounced. trigger="blur" runs on field blur. For checkout flows submit is almost always right.

What to persist

Recommended schema for your addresses table:

Column Source
raw_input What the user typed (audit trail)
country ISO-3 lowercase
street standardized.street
house_number standardized.house_number
city standardized.city
state standardized.state
postcode standardized.postcode
formatted_address standardized.formatted_address
lat, lng result.lat, result.lng
accuracy_type result.accuracy_type
confidence result.confidence
validated_at now()
acuris_status result.status (e.g. "V2")

Storing both the raw input and the standardized form means later you can re-validate against an improved cascade and compare without losing what the user originally entered.

Edge cases