Opt-Out & Suppression Lists
A suppression list is the set of addresses you must never message again — people who replied STOP, unsubscribed, bounced, or complained. Honouring it is a legal requirement on every regulated channel, and Orbit treats it as a hard send-gate: a suppressed address is dropped before dispatch regardless of campaign, contact import, or API call. This page covers how suppression works, how to bulk-import an existing suppression list — for example when migrating from another platform — through a single CSV upload, and how to export the ledger back out for an audit. All endpoints below are rooted athttps://api.orbit.devotel.io/api/v1/compliance.
How suppression happens
An address lands on the suppression list in several ways:- A contact replies with a STOP keyword on SMS/WhatsApp.
- A contact opts out via the Preference Center.
- You record an opt-out through the
Consent API (
opt_in: false). - You bulk-import a list (this page).
all,
sms, voice, whatsapp, email, push, telegram, messenger,
rcs.
How the scope is chosen depends on the entry point:
- Bulk CSV import infers the scope from the address type of each
row: phone and WhatsApp addresses default to scope
all— a STOP signal on a phone number suppresses every channel reachable on that number — while email addresses are scoped toemail. Achannelcolumn overrides this per row (see Bulk CSV import). - The Consent API and the Preference Center always suppress scope
all, regardless of whether the recorded identifier is a phone number or an email address. An opt-out through either of these entry points removes the contact from every channel.
However a phone number is suppressed, the voice and dialer gates honour
it: a number that opts out on any channel stops receiving calls as well
as messages. The mechanism differs by entry point. A bulk CSV import
additionally mirrors phone rows onto the DNC list and flags the matching
contacts. A STOP keyword, Preference Center, or Consent API
opt-out is instead recorded with the
all scope, which the voice and
dialer gates read from the suppression list directly — the call is still
blocked, but no separate DNC-list row or contact flag is written.Bulk CSV import
POST /compliance/suppression-list/import accepts a
multipart/form-data upload of a CSV file. It requires an admin or
owner key and is rate-limited to 5 requests/minute.
Form fields
CSV format
The first row is a header. Column names are case-insensitive and position-independent, and common aliases are accepted:
Each row must contain at least one of phone / email / wa_id. A
single row may carry several address types — each produces its own
suppression entry. Example:
channel column is present it overrides the default scope for
that row and must be one of the scope values listed above.
Per-row results
The response reports outcomes per row. Accepted rows are written; others are classified, never silently dropped.The two duplicate counters are reported separately and on
purpose:
intra_file_duplicates are repeats inside the file you
just uploaded, while duplicates were already on your list from
earlier. Neither is an error, and neither is silently swallowed —
both are counted so your reconciliation adds up.Validation reasons
Eacherrors[] entry carries a friendly reason and the source
raw_line so you can fix and re-upload:
Limits
For volumes beyond 100,000 rows, split the file and import in batches —
duplicate detection means re-importing overlapping ranges is safe.
When an import fails
Diagnose failures on two levels: HTTP-level rejections (nothing is written) and row-level classifications (the file is accepted but specific rows are not). HTTP-level rejections:
Row-level classifications (the
errors[] entries that accompany a
successful import) map to causes as follows:
Only the first 100
errors[] entries are returned with full detail —
the invalid counter always reflects the true total. The dashboard
wizard (below) packages the surfaced rows as a downloadable
skipped.csv so you can fix and re-import just the failures.
Import from the dashboard
The same endpoint is wrapped by a guided wizard at Settings → Compliance → Opt-out lists → Import suppression list — the same CSV contract, no terminal required.- Choose CSV file — pick a
.csvunder 25 MB. Use Download sample CSV in the dialog for a pre-formatted starting file. - Set defaults (optional) — a
Default country(ISO alpha-2) to normalise national-format numbers, and a free-textReasonstamped onto every accepted row. - Preview — runs the import as a server-side dry run: nothing is written, and the dialog shows the accepted / already-listed / repeats-in-file / invalid breakdown before you commit.
- Confirm import — performs the committing write. If any rows were
invalid, download
skipped.csvto fix and re-import them.
Export the suppression list
GET /compliance/suppression-list/export downloads the suppression
ledger — the symmetric counterpart to the import
above. Use it to prove to a regulator or auditor which addresses were
suppressed at a given point, including bulk-imported numbers with no
matching contact.
Each row carries
suppression_id, channel, address, the derived
status (active or revoked), reason, source, contact_id
(empty for bulk-imported addresses with no contact), notes, and the
suppressed_at / revoked_at / created_at timestamps.
Access is restricted to owner and admin keys, and every export
run is written to the audit log. When the ledger exceeds 50,000 rows
the CSV response carries an X-Export-Truncated: true header (the
JSON equivalent sets truncated: true) — narrow by channel or export
consecutive date windows to capture the tail.
Verify a suppression took effect
Trust but verify: after an import (or any opt-out event), confirm the send gate actually fences the address before you hand the list to a campaign.-
Send a test message to the suppressed address. A direct API send
to a suppressed recipient fails synchronously with HTTP 422 and the
error code
RECIPIENT_OPTED_OUT. In sandbox mode no carrier is touched and no balance is deducted; any recipient ending in8also resolves to the simulatedblockeddelivery receipt, which is the carrier-side view of the same fence.A campaign send to the same address behaves differently by design: the recipient is skipped silently (status: "skipped",reason: "opted_out") so the batch keeps moving — check the campaign’s per-recipient report rather than expecting an error. -
Confirm the entry is on the ledger. Export with
the query above (
status=activeis the default) and check the address appears with the expectedchannelscope. The export is the source of truth every send gate reads — if the row isactivethere, the fence is up.
Removing a suppression (re-opt-in)
To bring an address back, record a fresh opt-in through the Consent API (opt_in: true). That
revokes the matching suppression entry and clears the STOP-fence. Never
re-message a previously suppressed contact without a documented, fresh
consent event.
Confirming the fence is down. Export with status=revoked and
find the address: the row is still there for audit with
status: revoked and a revoked_at timestamp — suppression history is
never deleted, only revoked. Then send a small test message to the
address as in Verify a suppression
took effect: a successful
submission (no RECIPIENT_OPTED_OUT) confirms the gate no longer
fires on revoked history. Until both checks pass, treat the address as
still fenced.
Related references
- Consent Management — record and
look up per-channel consent, and the revocation-parity treatment this
page’s scope table is a part of: why a keyword STOP asserts scope
all, and how/compliance/consent/historyevidences the revocation endpoint the fence was written from. - Send Gates — quiet hours, DNC, RND, RMD, emergency stop, and the preference center.
- DSAR — how
delete/opt_outrequests reach suppression. - API Reference → Opt-outs — opt-out and suppression endpoint schemas.