API reference/address

Report what happened to post you sent

POST/api/v1/address/feedback

authentication
Secret API key as a bearer token
billing
Free.

Tells Spaw what became of post your account sent to an address: delivered, returned (it came back), moved (the occupant is no longer there) or refused (delivery was refused). Your own reports feed your own later lookups of that address, with the dataset address-feedback: an address whose most recent report within 90 days is returned, moved or refused rather than delivered scores reported_returned (+50), and a delivery reported afterwards cancels it.

Reports also keep your postal suppression list: a moved or refused report suppresses the address at once, a second returned report with no later delivery suppresses it, and a delivered report clears an entry your reports added.

There is deliberately no cross-account reputation here and no provider webhook. A postal address is where somebody lives, and a shared "post fails here" database would be a database about residences. What you report raises the risk of your own lookups and of nobody else's, and no other account ever sees it.

Each item is a written address or the separated fields, and may name its own country, which wins over the request-level one. Addresses are folded the way a lookup folds them, so any spelling of one building lines up; an item that names no building — no postcode or locality, no street, no house number — is skipped and counted, not rejected. Feedback is free, never billed and never logged as a lookup. Reports are kept for 180 days.

Request body

field type description
itemsrequiredobject[]1 to 1,000 reports per request.
items[].addressstring | nullThe whole address written as it would be on an envelope. Required unless address_line1 is sent. At most 500 characters.
items[].address_line1string | nullThe street line, when the address arrives separated. Required unless address is sent. At most 255 characters.
items[].address_line2string | nullAt most 255 characters.
items[].organizationstring | nullAt most 200 characters.
items[].dependent_localitystring | nullAt most 100 characters.
items[].localitystring | nullAt most 100 characters.
items[].administrative_areastring | nullAt most 100 characters.
items[].postal_codestring | nullAt most 32 characters.
items[].po_boxstring | nullAt most 64 characters.
items[].countrystring | nullThis item's own country; overrides the request-level one. At most 2 characters.
items[].outcomerequiredstringOne of: delivered, returned, moved, refused.
items[].occurred_atstring | nullWhen it happened. Defaults to now.
countrystring | nullAn ISO 3166-1 alpha-2 code applied to items that do not name their own. At most 2 characters.

Example request

curl -X POST https://spaw.co/api/v1/address/feedback \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "country": "GB",
  "items": [
    {
      "address": "221B Baker Street, London NW1 6XE",
      "outcome": "returned"
    },
    {
      "address_line1": "10 Downing Street",
      "locality": "London",
      "postal_code": "SW1A 2AA",
      "outcome": "moved",
      "occurred_at": "2026-09-05T09:00:00Z"
    }
  ]
}'
const response = await fetch('https://spaw.co/api/v1/address/feedback', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "country": "GB",
    "items": [
      {
        "address": "221B Baker Street, London NW1 6XE",
        "outcome": "returned"
      },
      {
        "address_line1": "10 Downing Street",
        "locality": "London",
        "postal_code": "SW1A 2AA",
        "outcome": "moved",
        "occurred_at": "2026-09-05T09:00:00Z"
      }
    ]
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/address/feedback',
    headers={'Authorization': 'Bearer sk_live_…'},
    json={
        'country': 'GB',
        'items': [
            {
                'address': '221B Baker Street, London NW1 6XE',
                'outcome': 'returned'
            },
            {
                'address_line1': '10 Downing Street',
                'locality': 'London',
                'postal_code': 'SW1A 2AA',
                'outcome': 'moved',
                'occurred_at': '2026-09-05T09:00:00Z'
            }
        ]
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/address/feedback');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_…', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS => json_encode([
        'country' => 'GB',
        'items' => [
            [
                'address' => '221B Baker Street, London NW1 6XE',
                'outcome' => 'returned'
            ],
            [
                'address_line1' => '10 Downing Street',
                'locality' => 'London',
                'postal_code' => 'SW1A 2AA',
                'outcome' => 'moved',
                'occurred_at' => '2026-09-05T09:00:00Z'
            ]
        ]
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

202The reports were recorded.

{
    "success": true,
    "data": {
        "recorded": 2,
        "skipped": 0
    }
}

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

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