API reference/feedback

Report delivery outcomes

POST/api/v1/email/feedback

authentication
Secret API key as a bearer token
billing
Free.

Tells Spaw what actually happened to an address after you sent to it: delivered, bounced (hard bounces only), or complained. Each outcome is stored against the verdict you had been given for that address in the previous 90 days, which turns verdicts into measured accuracy, and it keeps your suppression list current: a bounce or complaint adds the address with source feedback, a delivery removes an entry Spaw created automatically. An address you reported delivered within the last 90 days also counts as a confirmed mailbox for your own later lookups (smtp_reason: "delivered_recently"), without a probe.

Feedback is free and never logged as a lookup. Items whose address does not parse are skipped and counted, not rejected. Outcomes are kept for 180 days.

Request body

field type description
itemsrequiredobject[]1 to 1,000 outcomes per request.
items[].emailrequiredstringThe recipient. Normalized like a lookup; an address that does not parse is skipped and counted, not rejected. At most 254 characters.
items[].outcomerequiredstringWhat happened. Report hard bounces only. One of: delivered, bounced, complained.
items[].reasonstring | nullThe provider's diagnostic, e.g. the SMTP reply. Stored as-is. At most 255 characters.
items[].occurred_atstring | nullWhen it happened, any parseable date. Defaults to now.

Example request

curl -X POST https://spaw.co/api/v1/email/feedback \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "email": "[email protected]",
      "outcome": "bounced",
      "reason": "550 5.1.1 no such user"
    },
    {
      "email": "[email protected]",
      "outcome": "delivered",
      "occurred_at": "2026-09-03T09:00:00Z"
    }
  ]
}'
const response = await fetch('https://spaw.co/api/v1/email/feedback', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer sk_live_…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "items": [
      {
        "email": "[email protected]",
        "outcome": "bounced",
        "reason": "550 5.1.1 no such user"
      },
      {
        "email": "[email protected]",
        "outcome": "delivered",
        "occurred_at": "2026-09-03T09:00:00Z"
      }
    ]
  }),
});
const result = await response.json();
import requests

response = requests.post(
    'https://spaw.co/api/v1/email/feedback',
    headers={'Authorization': 'Bearer sk_live_…'},
    json={
        'items': [
            {
                'email': '[email protected]',
                'outcome': 'bounced',
                'reason': '550 5.1.1 no such user'
            },
            {
                'email': '[email protected]',
                'outcome': 'delivered',
                'occurred_at': '2026-09-03T09:00:00Z'
            }
        ]
    },
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/email/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' => [
            [
                'email' => '[email protected]',
                'outcome' => 'bounced',
                'reason' => '550 5.1.1 no such user'
            ],
            [
                'email' => '[email protected]',
                'outcome' => 'delivered',
                'occurred_at' => '2026-09-03T09: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