- authentication
- Secret API key as a bearer token
- billing
- Each address bills like a single lookup: 1 credit for a fresh answer that stands as written; addresses that cannot, and 7-day repeats, are free. The call stops cleanly where the balance ends.
Runs the single-address lookup for every item, in input order, under the exact single-lookup billing rules. Every answer comes from the installed datasets, so no item waits on a network call. data.results[] mirrors the single-address response per item and each item's meta carries credits_used and cache_hit.
Each item is either a written address as a plain string, or an object of the same fields the single lookup takes. An item may name its own country, which wins over the list-level one; an item left with no country at all is answered missing_country, free, rather than refused.
Repeats inside the list are looked up once and answer as cache hits — the repeat marker is keyed by the address as the pipeline folds it, so two spellings of one address count as one. The list is walked in order and stops where the balance would run out: running out of credits mid-batch returns the paid partial results with meta.stopped_reason: "insufficient_credits" and processed lower than requested, never discarding billed work. Only a batch whose first lookup is refused answers the typed 402.
A batch counts as one request against the rate limit. deliverability asks the licensed partner about every item; the base and premium credits are gated per item before the lookup, so no paid query is spent on an address that could not be charged.
Batches honour the account's postal suppression list: an address on it is answered from the list without being read again, free, with meta.suppressed: true on that item.
Request body
| field | type | description |
|---|---|---|
| addressesrequired | string[] | 1 to 100 addresses. Each item is a written address as a string, or an object of the same fields the single lookup takes. |
| addresses[].address | string | null | The whole address written as it would be on an envelope. At most 500 characters. |
| addresses[].address_line1 | string | null | The street line, when the address arrives separated. At most 255 characters. |
| addresses[].address_line2 | string | null | At most 255 characters. |
| addresses[].organization | string | null | At most 200 characters. |
| addresses[].dependent_locality | string | null | At most 100 characters. |
| addresses[].locality | string | null | At most 100 characters. |
| addresses[].administrative_area | string | null | At most 100 characters. |
| addresses[].postal_code | string | null | At most 32 characters. |
| addresses[].po_box | string | null | At most 64 characters. |
| addresses[].country | string | null | This item's own country. Wins over the list-level one. At most 2 characters. |
| country | string | null | An ISO 3166-1 alpha-2 code applied to every item that does not name its own. At most 2 characters. |
| deliverability | boolean | Ask the licensed partner about every address. While no partner is enabled it answers `deliverability_checked` false at no extra cost. Default: . |
Example request
curl -X POST https://spaw.co/api/v1/address/batch \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"addresses": [
"1600 Amphitheatre Parkway, Mountain View, CA 94043",
{
"address_line1": "221B Baker Street",
"locality": "London",
"postal_code": "NW1 6XE",
"country": "GB"
},
94043
],
"country": "US"
}'Responses
200One result per processed address, plus the batch totals.
{
"success": true,
"data": {
"results": [
{
"data": {
"valid": true,
"reason": null,
"country": "US",
"street": "Amphitheatre Parkway",
"house_number": "1600",
"locality": "Mountain View",
"administrative_area": "CA",
"postal_code": "94043",
"address_type": "street",
"risk_score": 0,
"risk_level": "low"
},
"meta": {
"credits_used": 1,
"cache_hit": false
}
},
{
"data": {
"valid": true,
"reason": null,
"country": "GB",
"street": "Baker Street",
"house_number": "221B",
"locality": "London",
"postal_code": "NW1 6XE",
"address_type": "street",
"risk_score": 0,
"risk_level": "low"
},
"meta": {
"credits_used": 1,
"cache_hit": false
}
},
{
"data": {
"valid": false,
"reason": "missing_street",
"country": "US",
"street": null,
"postal_code": "94043",
"risk_score": null,
"risk_level": null
},
"meta": {
"credits_used": 0,
"cache_hit": false
}
}
]
},
"meta": {
"requested": 3,
"processed": 3,
"credits_used": 2,
"credits_remaining": 8,
"stopped_reason": null,
"request_id": "req_01m1kgdrtqdvwnks99vfgx2rcw"
}
}401The key is missing, malformed, or revoked.
{
"success": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Provide a valid API key as a bearer token.",
"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"
}
}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"
}
}429Over 5 requests per second for the key. Retry after the limit resets.
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Too many requests. Retry after the limit resets.",
"request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
}
}Error codes
Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.