# 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.
- Group: Address

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 | Required | Description |
| --- | --- | --- | --- | --- |
| `Origin` | header | string | yes | Sent by browsers automatically. Its host must be on the key's allowed-domain list. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `key` | string | yes | A publishable key, which starts with `pk_`. At most 64 characters. |
| `address` | string | null | no | The whole address written as it would be on an envelope. Required unless `address_line1` is sent. At most 500 characters. |
| `address_line1` | string | null | no | The street line. Required unless `address` is sent. At most 255 characters. |
| `address_line2` | string | null | no | At most 255 characters. |
| `organization` | string | null | no | At most 200 characters. |
| `dependent_locality` | string | null | no | At most 100 characters. |
| `locality` | string | null | no | At most 100 characters. |
| `administrative_area` | string | null | no | At most 100 characters. |
| `postal_code` | string | null | no | At most 32 characters. |
| `po_box` | string | null | no | At most 64 characters. |
| `country` | string | yes | An ISO 3166-1 alpha-2 country code, usually from the country select on the same form. At most 2 characters. |
| `turnstile_token` | string | null | no | Required 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

```bash
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"
}'
```

## Responses

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

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

### 401 — The publishable key does not exist or was revoked.

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

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

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

### 403 — The 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`).

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

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

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

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

```json
{
    "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

- `INVALID_PUBLISHABLE_KEY` — https://spaw.co/docs/errors/INVALID_PUBLISHABLE_KEY
- `ORIGIN_NOT_ALLOWED` — https://spaw.co/docs/errors/ORIGIN_NOT_ALLOWED
- `TURNSTILE_FAILED` — https://spaw.co/docs/errors/TURNSTILE_FAILED
- `KEY_SPEND_CAP_REACHED` — https://spaw.co/docs/errors/KEY_SPEND_CAP_REACHED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED

---

Canonical page: https://spaw.co/docs/api/validate-address-public · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
