API reference/address

Validate an address from the browser with a publishable key

POST/api/v1/address/public

authentication
Publishable key in the body, checked against the browser Origin header
billing
Bills the key owner like a single lookup; capped by the key's daily credit cap when one is set.

The browser counterpart of POST /api/v1/address, for checking an address field in a checkout or signup form on the client without exposing a secret key. It is authenticated by a publishable pk_ key in the body plus the browser's Origin header, which must match one of the domains the key is locked to. A missing Origin is rejected on purpose: servers use a secret key.

The body takes the same address fields as the secret-key endpoint, including the required country. The lookup bills the key's owner under the normal rules (a fresh address that stands as written costs one credit; addresses that cannot, and 7-day repeats, are free) and answers { "success", "data" } with no meta block, so page visitors never see the owner's balance. The licensed deliverability check is never offered here: a deliverability field in the body is ignored.

Because the key sits in page source, give it a daily credit cap in the dashboard: once the cap is spent the endpoint answers 429 KEY_SPEND_CAP_REACHED until the next day. Free answers never count toward the cap. A key that carries a Cloudflare Turnstile pair requires a confirmed turnstile_token and answers 403 TURNSTILE_FAILED without one, before any credit is spent.

Throttled at 20 requests per minute per IP.

Parameters

name in type description
OriginrequiredheaderstringSent by browsers automatically. Its host must be on the key's allowed-domain list.

Request body

field type description
keyrequiredstringA publishable key, which starts with `pk_`. At most 64 characters.
addressstring | nullThe whole address written as it would be on an envelope. Required unless `address_line1` is sent. At most 500 characters.
address_line1string | nullThe street line. Required unless `address` is sent. At most 255 characters.
address_line2string | nullAt most 255 characters.
organizationstring | nullAt most 200 characters.
dependent_localitystring | nullAt most 100 characters.
localitystring | nullAt most 100 characters.
administrative_areastring | nullAt most 100 characters.
postal_codestring | nullAt most 32 characters.
po_boxstring | nullAt most 64 characters.
countryrequiredstringAn ISO 3166-1 alpha-2 country code, usually from the country select on the same form. At most 2 characters.
turnstile_tokenstring | nullRequired when the key carries a Turnstile pair. A single-use token from the Turnstile widget on the page. At most 2,048 characters.

Example request

curl -X POST https://spaw.co/api/v1/address/public \
  -H "Origin: https://www.example.com" \
  -H "Content-Type: application/json" \
  -d '{
  "key": "pk_live_…",
  "address_line1": "221B Baker Street",
  "locality": "London",
  "postal_code": "NW1 6XE",
  "country": "GB"
}'
const response = await fetch('https://spaw.co/api/v1/address/public', {
  method: 'POST',
  headers: {
    'Origin': 'https://www.example.com',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "key": "pk_live_…",
    "address_line1": "221B Baker Street",
    "locality": "London",
    "postal_code": "NW1 6XE",
    "country": "GB"
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/address/public',
    headers={'Origin': 'https://www.example.com'},
    json={
        'key': 'pk_live_…',
        'address_line1': '221B Baker Street',
        'locality': 'London',
        'postal_code': 'NW1 6XE',
        'country': 'GB'
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/address/public');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Origin: https://www.example.com', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'key' => 'pk_live_…',
        'address_line1' => '221B Baker Street',
        'locality' => 'London',
        'postal_code' => 'NW1 6XE',
        'country' => 'GB'
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

200The same fields as `POST /api/v1/address`, without a meta block.

{
    "success": true,
    "data": {
        "valid": true,
        "reason": null,
        "country": "GB",
        "street": "Baker Street",
        "house_number": "221B",
        "locality": "London",
        "postal_code": "NW1 6XE",
        "postal_code_valid": true,
        "postal_code_type": "postal",
        "address_type": "street",
        "is_po_box": false,
        "deliverability_checked": false,
        "risk_score": 0,
        "risk_level": "low"
    }
}

401The publishable key does not exist or was revoked.

{
    "success": false,
    "error": {
        "code": "INVALID_PUBLISHABLE_KEY",
        "message": "That publishable key does not exist or was revoked.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

402The balance is empty. The lookup did not run.

{
    "success": false,
    "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "Your credit balance is empty.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

403The page's origin is not on the key's allowed-domain list (`ORIGIN_NOT_ALLOWED`), or the key requires a Turnstile token that was missing or not confirmed (`TURNSTILE_FAILED`).

{
    "success": false,
    "error": {
        "code": "ORIGIN_NOT_ALLOWED",
        "message": "This publishable key cannot be used from this origin.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

422The request body could not be validated; `error.errors` lists the fields.

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429The key has spent its owner's daily credit cap (`KEY_SPEND_CAP_REACHED`), or the IP exceeded 20 requests per minute (`RATE_LIMITED`).

{
    "success": false,
    "error": {
        "code": "KEY_SPEND_CAP_REACHED",
        "message": "This publishable key has reached its daily credit cap. The counter resets each day.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json