API reference/address

Queue a bulk address lookup run

POST/api/v1/address/bulk

authentication
Secret API key as a bearer token
billing
Each row bills like a single lookup as it is processed; addresses that cannot stand as written, repeats inside the run and 7-day repeats are free.

Queues up to 100,000 addresses and processes them in the background. Creation answers 202 with the job's id and status. Poll GET /api/v1/address/bulk/{jobId} for progress, then download the result CSV from GET /api/v1/address/bulk/{jobId}/results: one row per input address with every response field as a column. Jobs and their files are kept for 30 days.

Each item is a written address as a plain string, or an object of the same fields the single lookup takes, and may name its own country, which wins over the list-level one; an item left with no country is answered missing_country, free. Rows repeating an address already in the list are looked up once and counted in duplicate_count — the repeat marker folds case, punctuation and street-suffix wording away, so two spellings of one address count as one. A run that exhausts the balance stops with stopped_reason: "insufficient_credits" and keeps everything answered so far. Because nothing about an address lookup is stored, the input list and the result file are the only copies and both are deleted with the job.

With a webhook_url, the finish (completed, failed, or cancelled) is POSTed there as { "event": "bulk_address_job.finished", "job": { … } }, signed in X-Spaw-Signature with the HMAC-SHA256 of the raw body keyed with webhook_secret, exactly like bulk email, phone and IP runs. Send an Idempotency-Key header to make creation safe to retry: a repeat of the same request answers the job the first attempt created, as 200 with Idempotent-Replayed: true; the same key with a different request answers 409 IDEMPOTENCY_KEY_REUSED.

Parameters

name in type description
Idempotency-KeyheaderstringA value unique to this request, so a retry after a timeout answers the same job instead of queueing a second one. 1 to 128 characters.

Request body

field type description
addressesrequiredstring[]1 to 100,000 addresses. Each item is a written address as a string, or an object of the same fields the single lookup takes.
addresses[].addressstring | nullThe whole address written as it would be on an envelope. At most 500 characters.
addresses[].address_line1string | nullThe street line, when the address arrives separated. At most 255 characters.
addresses[].address_line2string | nullAt most 255 characters.
addresses[].organizationstring | nullAt most 200 characters.
addresses[].dependent_localitystring | nullAt most 100 characters.
addresses[].localitystring | nullAt most 100 characters.
addresses[].administrative_areastring | nullAt most 100 characters.
addresses[].postal_codestring | nullAt most 32 characters.
addresses[].po_boxstring | nullAt most 64 characters.
addresses[].countrystring | nullThis item's own country. Wins over the list-level one. At most 2 characters.
countrystring | nullAn ISO 3166-1 alpha-2 code applied to every row that does not name its own. At most 2 characters.
deliverabilitybooleanAsk the licensed partner about every row. While no partner is enabled it answers `deliverability_checked` false at no extra cost; when one is, each row that answers is charged the premium credits on top of the base credit. Default: .
webhook_urlstring | nullAn https URL to notify when the run finishes.

Example request

curl -X POST https://spaw.co/api/v1/address/bulk \
  -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"
    }
  ],
  "country": "US",
  "webhook_url": "https://example.com/hooks/spaw"
}'
const response = await fetch('https://spaw.co/api/v1/address/bulk', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "addresses": [
      "1600 Amphitheatre Parkway, Mountain View, CA 94043",
      {
        "address_line1": "221B Baker Street",
        "locality": "London",
        "postal_code": "NW1 6XE",
        "country": "GB"
      }
    ],
    "country": "US",
    "webhook_url": "https://example.com/hooks/spaw"
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/address/bulk',
    headers={'Authorization': 'Bearer sk_live_…'},
    json={
        'addresses': [
            '1600 Amphitheatre Parkway, Mountain View, CA 94043',
            {
                'address_line1': '221B Baker Street',
                'locality': 'London',
                'postal_code': 'NW1 6XE',
                'country': 'GB'
            }
        ],
        'country': 'US',
        'webhook_url': 'https://example.com/hooks/spaw'
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/address/bulk');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_…', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'addresses' => [
            '1600 Amphitheatre Parkway, Mountain View, CA 94043',
            [
                'address_line1' => '221B Baker Street',
                'locality' => 'London',
                'postal_code' => 'NW1 6XE',
                'country' => 'GB'
            ]
        ],
        'country' => 'US',
        'webhook_url' => 'https://example.com/hooks/spaw'
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

202The job was queued.

{
    "success": true,
    "data": {
        "job": {
            "id": 918,
            "status": "queued",
            "total": 2,
            "duplicate_count": 0,
            "processed": 0,
            "valid": 0,
            "invalid": 0,
            "low": 0,
            "medium": 0,
            "high": 0,
            "credits_used": 0,
            "stopped_reason": null,
            "cancel_requested": false,
            "country": "US",
            "deliverability": false,
            "webhook_status": null,
            "created_at": "2026-09-05T10:12:44+00:00",
            "finished_at": null,
            "webhook_secret": "8fJ2…40 characters…Qk1"
        }
    }
}

200A retry with an `Idempotency-Key` already used for this exact request: the job the first attempt created, with `Idempotent-Replayed: true`.

{
    "success": true,
    "data": {
        "job": {
            "id": 918,
            "status": "processing",
            "total": 2,
            "duplicate_count": 0,
            "processed": 1,
            "valid": 1,
            "invalid": 0,
            "low": 1,
            "medium": 0,
            "high": 0,
            "credits_used": 1,
            "stopped_reason": null,
            "cancel_requested": false,
            "country": "US",
            "deliverability": false,
            "webhook_status": null,
            "created_at": "2026-09-05T10:12:44+00:00",
            "finished_at": null
        }
    }
}

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"
    }
}

409The `Idempotency-Key` was already used for a different list, country, deliverability flag or webhook URL.

{
    "success": false,
    "error": {
        "code": "IDEMPOTENCY_KEY_REUSED",
        "message": "This Idempotency-Key was already used for a different request.",
        "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.

markdown version·openapi.json