# Report delivery outcomes

`POST /api/v1/email/feedback`

- Authentication: Secret API key as a bearer token
- Billing: Free.
- Group: Feedback

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 | Required | Description |
| --- | --- | --- | --- |
| `items` | object[] | yes | 1 to 1,000 outcomes per request. |
| `items[].email` | string | yes | The recipient. Normalized like a lookup; an address that does not parse is skipped and counted, not rejected. At most 254 characters. |
| `items[].outcome` | string | yes | What happened. Report hard bounces only. One of: delivered, bounced, complained. |
| `items[].reason` | string | null | no | The provider's diagnostic, e.g. the SMTP reply. Stored as-is. At most 255 characters. |
| `items[].occurred_at` | string | null | no | When it happened, any parseable date. Defaults to now. |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/email/feedback \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "items": [
    {
      "email": "mia@acme.com",
      "outcome": "bounced",
      "reason": "550 5.1.1 no such user"
    },
    {
      "email": "sam@example.org",
      "outcome": "delivered",
      "occurred_at": "2026-09-03T09:00:00Z"
    }
  ]
}'
```

## Responses

### 202 — The outcomes were recorded.

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

### 401 — The key is missing, malformed, or revoked.

```json
{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 422 — The request body could not be validated; `error.errors` lists the fields.

```json
{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 429 — Over 5 requests per second for the key. Retry after the limit resets.

```json
{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

## Error codes

- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED

---

Canonical page: https://spaw.co/docs/api/report-delivery-feedback · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
