# Unsubscribes and blocks

Who may not be emailed and why: unsubscribes, resubscribes with consent, manual blocks and unblocks.

## Unsubscribe an address

`POST /projects/{project_id}/unsubscribes`

The address gets no more campaigns or broadcasts. Transactional email still goes out. Works for addresses that are not contacts too.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `email` | string | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/unsubscribes" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "maya@example.com"
}'
```

### Response

```json 201
{
  "id": "qoaa8t3rup47p9pb0tdbm50fqo1xo5cv",
  "email": "maya@example.com",
  "source": "api",
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Resubscribe an address

`POST /projects/{project_id}/resubscribes`

Opts an address back in. It needs proof that the person asked, so every resubscribe records the consent it rests on.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `email` | string | Required |
| `consent` | object | Required |
| `consent.wording` | string \| null | Required |
| `consent.ip_address` | string \| null | Required |
| `consent.user_agent` | string \| null | Required |
| `consent.consented_at` | integer | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/resubscribes" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "maya@example.com",
  "consent": {
    "wording": "Yes, send me product updates from Acme.",
    "ip_address": "203.0.113.42",
    "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6)",
    "consented_at": 1789999940000
  }
}'
```

### Response

```json 201
{
  "id": "0xzmas6en5mtmo3oqsg5lo50djzdnbj0",
  "email": "maya@example.com",
  "consent_id": "ddlz2uhfkvml73ctyxv2kgafrfw0h9ny",
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Block an address

`POST /projects/{project_id}/manual-blocks`

The address gets no email at all, transactional included, until you unblock it.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `email` | string | Required |
| `reason` | string \| null | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/manual-blocks" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "spamtrap@example.com",
  "reason": "Known spam trap"
}'
```

### Response

```json 201
{
  "id": "wt1fd4mx82mux4b0pzcyc3edqmevxrvc",
  "email": "spamtrap@example.com",
  "reason": "Known spam trap",
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Unblock an address

`POST /projects/{project_id}/unblocks`

Clears the bounces, spam complaints and manual blocks recorded so far. An address that reported spam needs consent to be unblocked. An unsubscribe is cleared only by a resubscribe.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `email` | string | Required |
| `reason` | string \| null | Required |
| `consent` | object \| null | Required |
| `consent.wording` | string \| null | Required |
| `consent.ip_address` | string \| null | Required |
| `consent.user_agent` | string \| null | Required |
| `consent.consented_at` | integer | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/unblocks" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "maya@example.com",
  "reason": "Mailbox was full, now fixed",
  "consent": null
}'
```

### Response

```json 201
{
  "id": "qurtaebog43yq15i5latjpuu3xf6mzkp",
  "email": "maya@example.com",
  "reason": "Mailbox was full, now fixed",
  "consent_id": null,
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## Record consent

`POST /projects/{project_id}/consents`

Proof that an address opted in: the wording they agreed to, where from, and when. Kept as a record; it does not change who gets email.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |

### Body

Send JSON with every field.

| Field | Type | Notes |
| --- | --- | --- |
| `email` | string | Required |
| `wording` | string \| null | Required |
| `ip_address` | string \| null | Required |
| `user_agent` | string \| null | Required |
| `consented_at` | integer | Required |

```sh Request
curl -X POST "https://sendocado.com/api/v1/projects/$PROJECT_ID/consents" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "maya@example.com",
  "wording": "Yes, send me product updates from Acme.",
  "ip_address": "203.0.113.42",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6)",
  "consented_at": 1789999940000
}'
```

### Response

```json 201
{
  "id": "ddlz2uhfkvml73ctyxv2kgafrfw0h9ny",
  "email": "maya@example.com",
  "source": "api",
  "wording": "Yes, send me product updates from Acme.",
  "ip_address": "203.0.113.42",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6)",
  "consented_at": 1789999940000,
  "created_at": 1790000000000
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | Send a JSON request body. |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |

## List unsubscribed and blocked addresses

`GET /projects/{project_id}/blocked-emails`

One reason per request, newest first. is_still_blocked is false once a later unblock or resubscribe cleared the row; email is null after an erasure.

### Parameters

| Name | In | Type | Notes |
| --- | --- | --- | --- |
| `project_id` | path | string | Required |
| `reason` | query | "unsubscribed" \| "bounced" \| "complained" \| "manual" | Required |
| `cursor` | query | string \| null | Optional |
| `limit` | query | integer | Optional, default 50 |

```sh Request
curl "https://sendocado.com/api/v1/projects/$PROJECT_ID/blocked-emails?reason=bounced" \
  -H "Authorization: Bearer $SENDOCADO_API_KEY"
```

### Response

```json 200
{
  "blocked_emails": [
    {
      "id": "wt1fd4mx82mux4b0pzcyc3edqmevxrvc",
      "email": "old-address@example.com",
      "reason": "bounced",
      "detail": "550 5.1.1 The email account that you tried to reach does not exist.",
      "is_still_blocked": true,
      "contact_id": "eh60kvj50ce9uvw53efr4edt2sywb3wk",
      "created_at": 1790000000000
    }
  ],
  "next_cursor": null
}
```

### Errors

| Status | Type | Example message |
| --- | --- | --- |
| 400 | `invalid_data` | limit: Too big: expected number to be <=200 |
| 401 | `unauthorized` | Send a valid API key as "Authorization: Bearer sc_…", or sign in. |
| 404 | `project_not_found` | Project not found. |
| 429 | `rate_limited` | Too many requests. Try again in 12 seconds. |
