API reference/phone

Report SMS outcomes

POST/api/v1/phone/feedback

authentication
Secret API key as a bearer token
billing
Free.

Tells Spaw what happened after you sent an SMS to a number: delivered, undelivered (the carrier reported it could not be delivered), or abused (the code was used by someone who was not the recipient, or the number was involved in fraud). Your own reports feed your own later lookups of that number as risk signals with the dataset delivery-feedback: a number whose latest report within 90 days is undelivered scores reported_undelivered (+50), a number with any abuse report within 90 days scores reported_abuse (+60), and a delivery reported after a failure cancels that failure. No other account ever sees your reports.

Numbers are normalised like a lookup; national notation needs a country on the item or on the request. Items whose number does not parse as a valid subscriber number are skipped and counted, not rejected. Feedback is free and never logged as a lookup. Outcomes are kept for 180 days.

Request body

field type description
itemsrequiredobject[]1 to 1,000 outcomes per request.
items[].phonerequiredstringThe number, in any common notation. At most 32 characters.
items[].outcomerequiredstringOne of: delivered, undelivered, abused.
items[].countrystring | nullAn ISO 3166-1 alpha-2 region for a number written without a calling code; overrides the request-level country.
items[].occurred_atstring | nullWhen it happened. Defaults to now.
countrystring | nullAn ISO 3166-1 alpha-2 region applied to items that do not name their own, for numbers written without a calling code.

Example request

curl -X POST https://spaw.co/api/v1/phone/feedback \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "phone": "+44 7911 012345",
      "outcome": "undelivered"
    },
    {
      "phone": "(415) 555-0142",
      "country": "US",
      "outcome": "delivered",
      "occurred_at": "2026-09-05T09:00:00Z"
    }
  ]
}'
const response = await fetch('https://spaw.co/api/v1/phone/feedback', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "items": [
      {
        "phone": "+44 7911 012345",
        "outcome": "undelivered"
      },
      {
        "phone": "(415) 555-0142",
        "country": "US",
        "outcome": "delivered",
        "occurred_at": "2026-09-05T09:00:00Z"
      }
    ]
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/phone/feedback',
    headers={'Authorization': 'Bearer sk_live_…'},
    json={
        'items': [
            {
                'phone': '+44 7911 012345',
                'outcome': 'undelivered'
            },
            {
                'phone': '(415) 555-0142',
                'country': 'US',
                'outcome': 'delivered',
                'occurred_at': '2026-09-05T09:00:00Z'
            }
        ]
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/phone/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([
        'items' => [
            [
                'phone' => '+44 7911 012345',
                'outcome' => 'undelivered'
            ],
            [
                'phone' => '(415) 555-0142',
                'country' => 'US',
                'outcome' => 'delivered',
                'occurred_at' => '2026-09-05T09:00:00Z'
            ]
        ]
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Responses

202The outcomes 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