Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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 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
1
2
curl "https://scaisend.scailabs.ai/v3/suppression/bounces?limit=100&start_time=1704067200" \
  -H "Authorization: Bearer $SCAISEND_API_KEY"
python
1
2
3
4
5
6
7
8
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
1
2
3
4
5
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "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
1
2
3
4
5
6
7
8
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
1
2
3
4
5
# 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
1
2
3
4
5
6
7
{
  "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
1
2
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
1
2
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 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
1
2
3
4
5
6
7
8
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
1
2
3
4
5
6
7
8
{
  "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
1
2
curl https://scaisend.scailabs.ai/v3/asm/groups \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

Update a group#

bash
1
2
3
4
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
1
2
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
1
2
curl "https://scaisend.scailabs.ai/v3/asm/groups/123/suppressions?page=1&page_size=100" \
  -H "Authorization: Bearer $SCAISEND_API_KEY"

Response:

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

Add to a group#

bash
1
2
3
4
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
1
2
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
1
2
3
4
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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#

Updated 2026-05-17 01:33:26 View source (.md) rev 1