# Validate and enrich a postal address

`POST /api/v1/address`

- Authentication: Secret API key as a bearer token
- Billing: 1 credit for a fresh lookup that answers `valid` true; addresses that cannot stand as written and 7-day repeats are free. Two spellings of one address share the repeat marker, so they are charged once. Asking for the deliverability check adds 3 credits only when the partner actually answered.
- Group: Address

Reads a postal address against the country it is addressed to: that operator's own format (which parts it requires, how the lines are ordered, what shape the postcode takes), its naming for boxes and other non-street delivery points, the national address registers and postcode directories, the open company registers, and the branch lists mailbox operators publish themselves.

Send the address either as one written `address`, the way it would appear on an envelope, or as the separated fields (`address_line1`, `address_line2`, `organization`, `dependent_locality`, `locality`, `administrative_area`, `postal_code`, `po_box`). Both spellings may be combined: a field sent beside a written address wins over what the parser read out of it. `country` (ISO 3166-1 alpha-2) is always required, because an address can only be measured against a country's own format; a code the format table does not cover answers `422`.

The answer gives the address broken into its parts, `formatted` as that country writes it, whether the postcode matches the country's pattern (`postal_code_valid`) and, where the whole directory is installed, whether it exists (`postcode_status`) and whether the town written on the address is the town that postcode belongs to (`locality_matches_postcode`). `address_type` says what kind of delivery point it is (`street`, `po_box`, `private_mailbox`, `general_delivery`, `parcel_locker`, `rural_route`, `military`, `bulk_mail`) and `address_type_source` says what settled it. An address that cannot stand as written answers `valid: false` with a `reason`: `empty_address`, `missing_country`, `unknown_country`, `missing_street`, `missing_locality`, `missing_administrative_area`, `missing_postal_code`, `invalid_postal_code` or `postcode_not_found`.

Where a national address register is installed, `exists` answers whether the building itself is in it. It is `true` only on a premise match, `false` only when the register that answered claims to list every address in its country (`register_coverage: "complete"`) and does not carry this one, and `null` otherwise — including a register that covers only part of its country (`register_coverage: "partial"`, which is what the United States National Address Database is, because states share their data voluntarily). `match_level` says how close the register got (`premise`, `street`, `postcode`, `none`).

The `risk_score` sums the weights of the signals in `risk_signals` (undeliverable 80, private_mailbox 60, mail_drop 50, vacant 40, general_delivery 40, po_box 30, terminated_postcode 30, locality_mismatch 30, not_in_register 20, bulk_mail 20; 60 and above is `high`, 30 and above `medium`), each with the dataset that produced it. Being a post-office box is not scored where a box is how that country receives mail, and `not_in_register` fires only when `exists` is false, so a register covering part of a country never contributes to the score.

Every signal whose dataset is not installed answers `null`, which means "not evaluated" and never "no". `deliverability: true` asks a licensed partner whether post actually arrives at the address and whether the delivery point is a mail-receiving agency, vacant or residential; while no partner is enabled, `deliverability_checked` answers `false` and nothing extra is charged.

The product is about places, not people: no field says who lives or works at an address, and none is ever added.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | null | no | The whole address written as it would be on an envelope, with the lines separated by commas or newlines. Required unless `address_line1` is sent. At most 500 characters. |
| `address_line1` | string | null | no | The street line — house number and street name, in the order that country writes them. Required unless `address` is sent. At most 255 characters. |
| `address_line2` | string | null | no | A second line — a flat, suite or unit, or a dependent locality. A value that reads as a unit is moved into `unit`. At most 255 characters. |
| `organization` | string | null | no | The company or institution the address belongs to. At most 200 characters. |
| `dependent_locality` | string | null | no | A neighbourhood, village or district written below the town. At most 100 characters. |
| `locality` | string | null | no | The town or city. At most 100 characters. |
| `administrative_area` | string | null | no | The state, province or region, as a code or a name; it is folded to the code that country uses where one exists. At most 100 characters. |
| `postal_code` | string | null | no | The postcode or ZIP code. At most 32 characters. |
| `po_box` | string | null | no | A post-office box written on its own, when it is not part of the address lines. At most 64 characters. |
| `country` | string | yes | An ISO 3166-1 alpha-2 country code the postal format table covers (`US`, `GB`, `DE`). Case-insensitive. At most 2 characters. |
| `deliverability` | boolean | no | Ask the licensed partner whether post actually arrives at the address. Reserves the premium credits up front and charges them only when the partner answered. Default: . |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/address \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "address": "1600 Amphitheatre Parkway, Mountain View, CA 94043",
  "country": "US"
}'
```

## Responses

### 200 — The address as the pipeline reads it; `valid: false` with a `reason` for an address that cannot stand as written.

```json
{
    "success": true,
    "data": {
        "valid": true,
        "reason": null,
        "country": "US",
        "country_name": "United States",
        "continent": "NA",
        "is_eu": false,
        "is_eea": false,
        "privacy_regime": "ccpa",
        "formatted": "1600 Amphitheatre Parkway\nMOUNTAIN VIEW, CA 94043",
        "organization": null,
        "street": "Amphitheatre Parkway",
        "house_number": "1600",
        "unit": null,
        "po_box": null,
        "dependent_locality": null,
        "locality": "Mountain View",
        "administrative_area": "CA",
        "postal_code": "94043",
        "postal_code_valid": true,
        "postal_code_type": "zip",
        "postcode_status": "live",
        "locality_matches_postcode": true,
        "address_type": "street",
        "address_type_source": "address_rules",
        "is_po_box": false,
        "is_private_mailbox": null,
        "exists": null,
        "match_level": "street",
        "register": "us-national-address-database",
        "register_coverage": "partial",
        "latitude": 37.4224,
        "longitude": -122.0841,
        "location_source": "postcode_centroid",
        "companies_registered": null,
        "is_mail_drop": false,
        "mail_drop_provider": null,
        "mail_drop_source": null,
        "deliverability_checked": false,
        "is_deliverable": null,
        "is_cmra": null,
        "is_vacant": null,
        "is_residential": null,
        "risk_score": 0,
        "risk_level": "low",
        "risk_signals": [],
        "sources": [
            {
                "dataset": "address-formats",
                "version": "2026-09-05"
            },
            {
                "dataset": "address-rules",
                "version": "2026-09-05"
            },
            {
                "dataset": "us-national-address-database",
                "version": "2026-08-14"
            }
        ]
    },
    "meta": {
        "credits_used": 1,
        "credits_remaining": 9,
        "cache_hit": false,
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 401 — The key is missing, malformed, or revoked.

```json
{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "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"
    }
}
```

### 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 — Over 5 requests per second for the key. Retry after the limit resets.

```json
{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

## Error codes

- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `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 · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
