---
title: Suppressions
path: tutorials/suppressions
status: published
---

# Suppressions

Managing the bounce, spam-report, unsubscribe, and suppression-group lists through the API. For the conceptual overview — what each list means and when addresses are added automatically — read [Suppressions](../concepts/suppressions) first.

**Base paths:**
- `/v3/suppression/bounces`
- `/v3/suppression/spam_reports`
- `/v3/asm/suppressions/global` (global unsubscribes)
- `/v3/asm/groups` (suppression groups)

**Auth:** API key or JWT; `suppressions.read` for reads, `suppressions.write` for modifications.

## Bounces

### List bounces

```bash
curl "https://scaisend.scailabs.ai/v3/suppression/bounces?limit=100&start_time=1704067200" \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

```python
import os, httpx

resp = httpx.get(
    "https://scaisend.scailabs.ai/v3/suppression/bounces",
    headers={"Authorization": f"Bearer {os.environ['SCAISEND_API_KEY']}"},
    params={"limit": 100, "start_time": 1704067200},
)
bounces = resp.json()["bounces"]
```

```typescript
const resp = await fetch(
  "https://scaisend.scailabs.ai/v3/suppression/bounces?limit=100&start_time=1704067200",
  { headers: { "Authorization": `Bearer ${process.env.SCAISEND_API_KEY}` } },
);
const { bounces } = await resp.json();
```

Query parameters:

| Parameter | Notes |
|-----------|-------|
| `limit` | Results per page (default 50, max 500) |
| `offset` | Pagination offset |
| `start_time` / `end_time` | Unix timestamp range |
| `email` | Filter by a specific recipient |

Response:

```json
{
  "bounces": [
    {
      "email": "invalid@example.com",
      "created": 1714060800,
      "reason": "550 5.1.1 User unknown",
      "bounce_type": "hard",
      "status": "5.1.1"
    }
  ],
  "total": 152
}
```

### Add a bounce manually

Useful during migration — import bounce lists from your previous provider so you don't re-send to addresses that have already bounced.

```bash
curl -X POST https://scaisend.scailabs.ai/v3/suppression/bounces \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "invalid@example.com",
    "bounce_type": "hard",
    "reason": "Imported from SendGrid, 2026-03-01"
  }'
```

`bounce_type` values: `hard`, `soft`, `block`. The effect is the same — ScaiSend won't send to the address — but `bounce_type` is stored for your reporting.

### Bulk import

```bash
# CSV body with columns: email, reason (optional)
curl -X POST https://scaisend.scailabs.ai/v3/suppression/bounces/import \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @bounces.csv
```

Response includes counts and per-row errors:

```json
{
  "imported": 1482,
  "skipped": 12,
  "errors": [
    {"row": 44, "email": "not-an-email", "error": "Invalid email format"}
  ]
}
```

### Remove a bounce

Re-enable sending to an address:

```bash
curl -X DELETE https://scaisend.scailabs.ai/v3/suppression/bounces/invalid@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

204 on success. No body.

**Bulk delete all bounces:**

```bash
curl -X DELETE "https://scaisend.scailabs.ai/v3/suppression/bounces?delete_all=true" \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

The `delete_all=true` query parameter is required — without it, you get 400. Use cautiously.

## Spam reports

Same endpoints, different list:

```bash
# List
curl https://scaisend.scailabs.ai/v3/suppression/spam_reports \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

# Get a specific recipient's report(s)
curl https://scaisend.scailabs.ai/v3/suppression/spam_reports/abuser@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

# Add manually (rare; usually populated by FBL)
curl -X POST https://scaisend.scailabs.ai/v3/suppression/spam_reports \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "abuser@example.com", "source": "support_ticket_#4821"}'

# Remove (rare; removing a spam report is a policy decision, not cleanup)
curl -X DELETE https://scaisend.scailabs.ai/v3/suppression/spam_reports/abuser@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

## Global unsubscribes

These are the most powerful suppression — a globally unsubscribed address receives nothing from this tenant.

```bash
# List everyone who's globally unsubscribed
curl https://scaisend.scailabs.ai/v3/asm/suppressions/global \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

# Check a specific address
curl https://scaisend.scailabs.ai/v3/asm/suppressions/global/user@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
# -> 200 with {"recipient_email": "user@example.com"} if suppressed, 404 if not

# Add (e.g., responding to a "remove me from everything" ticket)
curl -X POST https://scaisend.scailabs.ai/v3/asm/suppressions/global \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipient_emails": ["user@example.com", "another@example.com"]}'

# Remove (they asked back in)
curl -X DELETE https://scaisend.scailabs.ai/v3/asm/suppressions/global/user@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

## Suppression groups

Groups let recipients opt out of a **type** of email rather than all email.

### Create a group

```bash
curl -X POST https://scaisend.scailabs.ai/v3/asm/groups \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Newsletter",
    "description": "Weekly roundup and product updates",
    "is_default": false
  }'
```

Response:

```json
{
  "id": 123,
  "name": "Newsletter",
  "description": "Weekly roundup and product updates",
  "is_default": false,
  "unsubscribes": 0,
  "created_at": "2026-04-23T10:00:00Z"
}
```

The numeric `id` is what you pass in `asm.group_id` on a send.

### List groups

```bash
curl https://scaisend.scailabs.ai/v3/asm/groups \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

### Update a group

```bash
curl -X PATCH https://scaisend.scailabs.ai/v3/asm/groups/123 \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description": "Updated description"}'
```

### Delete a group

```bash
curl -X DELETE https://scaisend.scailabs.ai/v3/asm/groups/123 \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

Deletes the group and all per-group suppressions. Messages already sent with this `group_id` still exist; the preference page they link to will show the group as "deleted" to any late clicker.

### List addresses suppressed in a group

```bash
curl "https://scaisend.scailabs.ai/v3/asm/groups/123/suppressions?page=1&page_size=100" \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

Response:

```json
{
  "recipient_emails": ["opted-out@example.com", "another@example.com"],
  "total": 2
}
```

### Add to a group

```bash
curl -X POST https://scaisend.scailabs.ai/v3/asm/groups/123/suppressions \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recipient_emails": ["opt-out@example.com", "also-opt-out@example.com"]}'
```

### Remove from a group

```bash
curl -X DELETE https://scaisend.scailabs.ai/v3/asm/groups/123/suppressions/opt-out@example.com \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
```

The recipient is re-subscribed to this group. A `group_resubscribe` event is emitted.

### Sending with a group

```json
{
  "personalizations": [{"to": [{"email": "user@example.com"}]}],
  "from": {"email": "hello@mail.example.com"},
  "subject": "This week in Acme",
  "content": [{"type": "text/html", "value": "<html>..."}],
  "asm": {
    "group_id": 123,
    "groups_to_display": [123, 124, 125]
  }
}
```

- `group_id` is the primary group this message belongs to. If the recipient is in this group's suppression list, the message is dropped with reason `suppressed_group_unsubscribe`.
- `groups_to_display` lists the groups to show on the unsubscribe preference page. Use it to let a recipient manage their subscriptions in one click.

## Recipes

### Clean import from another provider

```python
import csv
import os
import httpx

api_key = os.environ["SCAISEND_API_KEY"]
base = "https://scaisend.scailabs.ai"
headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json",
}

with open("sendgrid-export.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        suppression_type = row["type"]
        email = row["email"]

        if suppression_type == "bounce":
            httpx.post(
                f"{base}/v3/suppression/bounces",
                headers=headers,
                json={"email": email, "bounce_type": "hard", "reason": "Imported"},
            )
        elif suppression_type == "spam":
            httpx.post(
                f"{base}/v3/suppression/spam_reports",
                headers=headers,
                json={"email": email, "source": "migration"},
            )
        elif suppression_type == "unsubscribe":
            httpx.post(
                f"{base}/v3/asm/suppressions/global",
                headers=headers,
                json={"recipient_emails": [email]},
            )
```

Or use the bulk import endpoints — faster on large lists:

```bash
curl -X POST https://scaisend.scailabs.ai/v3/suppression/bounces/import \
  -H "Authorization: Bearer $SCAISEND_API_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @sendgrid-bounces.csv
```

### Weekly suppression-list audit

```python
from datetime import datetime, timedelta
import os, httpx

api_key = os.environ["SCAISEND_API_KEY"]
base = "https://scaisend.scailabs.ai"

week_ago = int((datetime.now() - timedelta(days=7)).timestamp())

resp = httpx.get(
    f"{base}/v3/suppression/bounces",
    headers={"Authorization": f"Bearer {api_key}"},
    params={"start_time": week_ago, "limit": 500},
)
new_bounces = resp.json()["bounces"]
print(f"{len(new_bounces)} new bounces this week")
for b in new_bounces:
    if b["bounce_type"] == "soft":
        print(f"  soft: {b['email']} — {b['reason']}")
```

Soft bounces are candidates for eventual removal (mailbox was temporarily full; the recipient is probably fine now). Hard bounces should stay.

## What's next

- [Suppressions (concept)](../concepts/suppressions) — the model underneath these endpoints.
- [Suppressions Reference](../reference/suppressions) — exhaustive field reference.
- [Events and Webhooks](../concepts/events-and-webhooks) — `unsubscribe`, `spam_report`, `bounce` events.
