Skip to content

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, and a delivery removes an entry a verdict or a bounce added. A complaint is the recipient's objection, not a fact about the mailbox: no delivery, lookup or later bounce clears or relabels it and it never goes stale, so like an import it leaves only when you remove it. On an address you imported, a complaint is recorded on the import (reason complained, source still imported) and a bounce renews it without changing its reason; a delivery never clears an import. 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. A time more than five minutes in the future is refused; one inside that margin is recorded as 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);
from spaw import Client

client = Client('sk_live_…')
result = client.report_feedback([
    {
        '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'
    }
])
import Spaw from 'spaw';

const spaw = new Spaw({ apiKey: 'sk_live_…' });
const result = await spaw.reportFeedback([
    {
        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'
    }
]);
use Spaw\Client;

$spaw = new Client('sk_live_…');
$result = $spaw->reportFeedback([
    [
        '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'
    ]
]);

This endpoint has no console on its page. It writes to your account. A documentation page can show you the request; making the change is for the dashboard or for a call you make yourself.

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