> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Opt-out & Unterdrückungslisten

> Wie Orbit abgemeldete Empfänger kanalübergreifend unterdrückt, wie Sie eine Unterdrückungsliste per CSV mit Zeilenergebnissen und Deduplizierung massenimportieren und wie Sie die Durchsetzung verifizieren, Importfehler diagnostizieren und eine Unterdrückung sicher rückgängig machen.

# Opt-out & Unterdrückungslisten

Eine **Unterdrückungsliste** ist die Menge der Adressen, die Sie nie
wieder anschreiben dürfen — Personen, die mit STOP geantwortet, sich
abgemeldet, einen Bounce erzeugt oder sich beschwert haben. Sie zu
beachten ist auf jedem regulierten Kanal eine gesetzliche Pflicht, und
Orbit behandelt sie als hartes Send-Gate: Eine unterdrückte Adresse
wird vor dem Versand verworfen, unabhängig von Kampagne,
Kontaktimport oder API-Aufruf.

Diese Seite behandelt, wie Unterdrückung funktioniert, wie Sie eine
bestehende Unterdrückungsliste **massenimportieren** — zum Beispiel bei
einer Migration von einer anderen Plattform — über einen einzigen
CSV-Upload, und wie Sie das Register für ein Audit wieder
[exportieren](#die-unterdrückungsliste-exportieren).

Alle Endpunkte unten sind unter
`https://api.orbit.devotel.io/api/v1/compliance` verwurzelt.

***

## Wie Unterdrückung entsteht

Eine Adresse landet auf mehreren Wegen auf der Unterdrückungsliste:

* Ein Kontakt antwortet mit einem **STOP-Keyword** auf SMS/WhatsApp.
* Ein Kontakt meldet sich über das [Preference Center](/compliance/send-gates#preference-center) ab.
* Sie erfassen ein Opt-out über die
  [Consent API](/compliance/consent-management) (`opt_in: false`).
* Sie **importieren eine Liste per Massenimport** (diese Seite).

Jeder Eintrag hat einen **Kanal-Geltungsbereich**. Der vollständige
Satz ist: `all`, `sms`, `voice`, `whatsapp`, `email`, `push`,
`telegram`, `messenger`, `rcs`.

Wie der Geltungsbereich gewählt wird, hängt vom Einstiegspunkt ab:

* **CSV-Massenimport** leitet den Geltungsbereich aus dem Adresstyp
  jeder Zeile ab: Telefon- und WhatsApp-Adressen erhalten standardmäßig
  den Geltungsbereich `all` — ein STOP-Signal auf einer Telefonnummer
  unterdrückt jeden auf dieser Nummer erreichbaren Kanal — während
  E-Mail-Adressen auf `email` beschränkt werden. Eine `channel`-Spalte
  überschreibt dies pro Zeile (siehe [CSV-Massenimport](#csv-massenimport)).
* **Die Consent API und das Preference Center** unterdrücken stets den
  Geltungsbereich `all`, unabhängig davon, ob die erfasste Kennung eine
  Telefonnummer oder eine E-Mail-Adresse ist. Ein Opt-out über einen
  dieser Einstiegspunkte entfernt den Kontakt aus jedem Kanal.

<Note>
  Wie auch immer eine Telefonnummer unterdrückt wurde: Die Voice- und
  Dialer-Gates beachten sie — eine Nummer, die auf irgendeinem Kanal ein
  Opt-out erteilt, erhält weder Anrufe noch Nachrichten. Der Mechanismus
  unterscheidet sich je nach Einstiegspunkt. Ein **CSV-Massenimport**
  spiegelt Telefonzeilen zusätzlich auf die DNC-Liste und markiert die
  passenden Kontakte. Ein **STOP-Keyword**-, **Preference-Center**- oder
  **Consent-API**-Opt-out wird stattdessen mit dem Geltungsbereich `all`
  erfasst, den die Voice- und Dialer-Gates direkt aus der
  Unterdrückungsliste lesen — der Anruf wird weiterhin blockiert, aber
  es wird keine separate DNC-Listenzeile oder Kontaktmarkierung
  geschrieben.
</Note>

***

## CSV-Massenimport

`POST /compliance/suppression-list/import` nimmt einen
`multipart/form-data`-Upload einer CSV-Datei entgegen. Er erfordert
einen Admin- oder Owner-Key und ist auf 5 Anfragen/Minute
ratenbegrenzt.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/suppression-list/import \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -F "file=@suppressions.csv" \
  -F "default_country=US" \
  -F "default_reason=migrated_from_legacy_platform" \
  -F "dry_run=false"
```

### Formularfelder

| Feld              | Typ     | Hinweise                                                                                                                          |
| ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `file`            | file    | **Erforderlich.** Eine einzelne CSV, ≤ 25 MB, ≤ 100.000 Zeilen.                                                                   |
| `default_country` | string  | ISO-3166-1 alpha-2. Dient dazu, nationale Telefonnummernformate nach E.164 zu normalisieren.                                      |
| `default_reason`  | string  | Wird auf jede akzeptierte Zeile angewendet (≤ 512 Zeichen).                                                                       |
| `dry_run`         | boolean | Bei `true` nur parsen und klassifizieren — keine Datenbankschreibzugriffe. Nutzen Sie es, um eine Datei vor dem Commit zu prüfen. |

### CSV-Format

Die erste Zeile ist eine Kopfzeile. Spaltennamen sind
**Groß-/Kleinschreibung-unabhängig** und **positionsunabhängig**, und
gängige Aliasse werden akzeptiert:

| Logische Spalte                  | Akzeptierte Header                         |
| -------------------------------- | ------------------------------------------ |
| Telefon                          | `phone`, `phonenumber`, `mobile`, `msisdn` |
| E-Mail                           | `email`, `emailaddress`, `mail`            |
| WhatsApp-ID                      | `wa_id`, `whatsapp`, `whatsappid`          |
| Grund (optional)                 | `reason`, `note`, `notes`                  |
| Kanal (optionale Überschreibung) | `channel`                                  |

Jede Zeile muss **mindestens eines** von Telefon / E-Mail / wa\_id
enthalten. Eine einzelne Zeile kann mehrere Adresstypen tragen — jeder
erzeugt seinen eigenen Unterdrückungseintrag. Beispiel:

```csv theme={null}
phone,email,reason
+14155550101,,replied STOP
,jordan@example.com,unsubscribed via email
+442071838750,sam@example.co.uk,complaint
```

Ist eine `channel`-Spalte vorhanden, überschreibt sie den
Standardgeltungsbereich für diese Zeile und muss einem der oben
genannten Geltungsbereichswerte entsprechen.

### Ergebnisse pro Zeile

Die Antwort meldet Ergebnisse pro Zeile. Akzeptierte Zeilen werden
geschrieben; andere werden klassifiziert, nie still verworfen.

```json theme={null}
{
  "data": {
    "run_id": "supimp_4d…",
    "total_rows": 1000,
    "accepted": 950,
    "duplicates": 30,
    "intra_file_duplicates": 15,
    "invalid": 5,
    "errors": [
      { "status": "invalid", "reason": "invalid_phone", "raw_line": 42 }
    ],
    "by_channel": { "all": 800, "email": 150 },
    "file_sha256": "9b2e…"
  },
  "meta": { "request_id": "…", "timestamp": "2026-06-08T12:00:00.000Z" }
}
```

| Zähler                  | Bedeutung                                                                                 |
| ----------------------- | ----------------------------------------------------------------------------------------- |
| `accepted`              | Neu in die Unterdrückungsliste geschriebene Zeilen.                                       |
| `intra_file_duplicates` | Zeilen, die ein früheres `(channel, address)` **innerhalb derselben Datei** wiederholen.  |
| `duplicates`            | Zeilen, die bereits aus einem **früheren** Import unterdrückt sind (übersprungen, No-op). |
| `invalid`               | Zeilen, die die Validierung nicht bestanden — siehe `errors[]`.                           |
| `by_channel`            | Akzeptierte Mengen, gruppiert nach Kanal-Geltungsbereich.                                 |
| `file_sha256`           | Inhaltshash des Uploads, für Audits aufgezeichnet.                                        |

<Note>
  Die beiden Duplikat-Zähler werden **bewusst getrennt** gemeldet:
  `intra_file_duplicates` sind Wiederholungen innerhalb der soeben
  hochgeladenen Datei, während `duplicates` bereits von früher auf
  Ihrer Liste standen. Keines ist ein Fehler, und keines wird still
  verschluckt — beide werden gezählt, damit Ihre Abstimmung aufgeht.
</Note>

### Validierungsgründe

Jeder `errors[]`-Eintrag trägt einen verständlichen `reason` und die
Quell-`raw_line`, damit Sie korrigieren und erneut hochladen können:

| `reason`           | Ursache                                                  |
| ------------------ | -------------------------------------------------------- |
| `missing_address`  | Die Zeile hatte weder Telefon, E-Mail noch wa\_id.       |
| `invalid_phone`    | Telefon konnte nicht nach E.164 normalisiert werden.     |
| `invalid_email`    | E-Mail hat die RFC-5321-Formvalidierung nicht bestanden. |
| `invalid_wa_id`    | WhatsApp-ID war keine gültige E.164-Nummer.              |
| `row_too_long`     | Eine Zelle überschritt 4096 Zeichen.                     |
| `too_many_columns` | Die Zeile hatte mehr als 32 Spalten.                     |

### Grenzen

| Grenze                  | Wert                                                                           |
| ----------------------- | ------------------------------------------------------------------------------ |
| Max. Dateigröße         | 25 MB                                                                          |
| Max. Zeilen pro Anfrage | 100.000                                                                        |
| Max. Zelllänge          | 4.096 Zeichen                                                                  |
| Max. Spalten pro Zeile  | 32                                                                             |
| Max. Grund-Länge        | 512 Zeichen                                                                    |
| Serverseitiges Timeout  | 60 s (ein Teilimport gibt `408` mit den bis dahin verarbeiteten Mengen zurück) |

Für Volumen über 100.000 Zeilen teilen Sie die Datei auf und
importieren in Batches — die Duplikaterkennung macht das erneute
Importieren überlappender Bereiche sicher.

### Wenn ein Import fehlschlägt

Diagnostizieren Sie Fehler auf zwei Ebenen: **Ablehnungen auf
HTTP-Ebene** (nichts wird geschrieben) und **Klassifizierungen auf
Zeilenebene** (die Datei wird akzeptiert, bestimmte Zeilen jedoch
nicht).

Ablehnungen auf HTTP-Ebene:

| Status                       | Bedeutung                                                                                                        | Behebung                                                                                                                                  |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `400 NO_FILE`                | Kein `file`-Teil im Multipart-Body.                                                                              | Hängen Sie die CSV als Multipart-Feld `file` an.                                                                                          |
| `400 MULTIPLE_FILES`         | Mehr als eine Datei angehängt.                                                                                   | Senden Sie eine CSV pro Anfrage.                                                                                                          |
| `400 CSV_PARSE_ERROR`        | Fehlerhaftes Quoting — ein nicht abgeschlossenes `"` oder eine fehlende Kopfzeile.                               | Als RFC-4180-CSV (UTF-8) erneut exportieren; prüfen, dass jedes Anführungszeichen geschlossen ist.                                        |
| `400 MULTIPART_PARSE_FAILED` | Der Body war kein gültiges multipart/form-data.                                                                  | `Content-Type: multipart/form-data` setzen und den Body nicht vorab kodieren.                                                             |
| `408 IMPORT_TIMEOUT`         | Der Lauf überschritt das 60-Sekunden-Budget. Die Antwort nennt die bereits unterdrückten Zeilen; der Rest nicht. | Teilen Sie den Rest in kleinere Dateien und führen Sie erneut aus — vor dem Timeout unterdrückte Zeilen werden als `duplicates` gemeldet. |
| `413 PAYLOAD_TOO_LARGE`      | Die Datei überschreitet 25 MB.                                                                                   | In Dateien unter je 25 MB aufteilen.                                                                                                      |
| `413 TOO_MANY_ROWS`          | Die geparste Datei überschreitet 100.000 Zeilen.                                                                 | In Dateien mit ≤ 100.000 Zeilen aufteilen.                                                                                                |
| `415 UNSUPPORTED_MEDIA_TYPE` | Der Upload war keine CSV (ein `.xlsx`-Export ist der übliche Auslöser).                                          | Als CSV (UTF-8) erneut exportieren.                                                                                                       |
| `422 MISSING_ADDRESS_COLUMN` | Die Kopfzeile enthält keine erkennbare Adressspalte.                                                             | Mindestens einen der Header `phone`, `email`, `wa_id` einfügen (Aliasse stehen unter [CSV-Format](#csv-format)).                          |
| `422 VALIDATION_ERROR`       | Ein optionales Formularfeld hat die Validierung nicht bestanden.                                                 | Prüfen Sie, dass `default_country` ein 2-Buchstaben-ISO-Code und `default_reason` ≤ 512 Zeichen ist.                                      |
| `429`                        | Mehr als 5 Importanfragen in einer Minute.                                                                       | Warten Sie, bis das Fenster schließt, dann erneut versuchen — stellen Sie Bulk-Batches in die Warteschlange statt zu hämmern.             |

Klassifizierungen auf Zeilenebene (die `errors[]`-Einträge, die einen
erfolgreichen Import begleiten) lassen sich wie folgt auf Ursachen
zurückführen:

| `reason`           | Ursache                                                                                                                           | Behebung                                                                                                              |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `missing_address`  | Die Zeile hatte weder Telefon, E-Mail noch wa\_id — oder ihre `channel`-Überschreibung war keiner der erlaubten Geltungsbereiche. | Mindestens eine Adresszelle füllen; die `channel`-Spalte auf die oben genannten Geltungsbereichswerte beschränken.    |
| `invalid_phone`    | Das Telefon konnte nicht nach E.164 normalisiert werden.                                                                          | Die Nummer korrigieren oder `default_country` übergeben, damit nationale Formate parsen.                              |
| `invalid_email`    | Die E-Mail hat die RFC-5321-Formvalidierung nicht bestanden (oder 254 Zeichen überschritten).                                     | Die Adresse korrigieren; verirrte Leerzeichen und ein fehlendes `@` sind die üblichen Verursacher.                    |
| `invalid_wa_id`    | Die WhatsApp-ID war keine gültige E.164-Nummer.                                                                                   | Die MSISDN des Empfängers in E.164-Form verwenden (führendes `+` optional).                                           |
| `row_too_long`     | Eine Zelle überschritt 4.096 Zeichen.                                                                                             | Die Zelle kürzen — meist ist ein eingefügter Blob in die falsche Spalte gerutscht.                                    |
| `too_many_columns` | Die Zeile hatte mehr als 32 Spalten.                                                                                              | Mit einem einzigen Trennzeichen erneut exportieren; ungesetzte Kommas in einer Zelle zerteilen sie in Phantonspalten. |

Nur die ersten 100 `errors[]`-Einträge werden mit vollem Detail
zurückgegeben — der `invalid`-Zähler spiegelt stets die wahre Summe.
Der Dashboard-Assistent (unten) bündelt die aufgeführten Zeilen als
herunterladbare `skipped.csv`, damit Sie genau die Fehler korrigieren
und erneut importieren können.

### Import aus dem Dashboard

Derselbe Endpunkt wird von einem geführten Assistenten unter
**Settings → Compliance → Opt-out lists → Import suppression list**
umhüllt — derselbe CSV-Vertrag, kein Terminal erforderlich.

1. **CSV-Datei wählen** — wählen Sie eine `.csv` unter 25 MB. Nutzen
   Sie **Download sample CSV** im Dialog für eine vorformatierte
   Startdatei.
2. **Defaults setzen (optional)** — ein `Default country` (ISO alpha-2)
   zur Normalisierung nationaler Formate und ein Freitext-`Reason`, der
   auf jede akzeptierte Zeile gestempelt wird.
3. **Vorschau** — führt den Import als serverseitigen Dry-Run aus:
   Nichts wird geschrieben, und der Dialog zeigt die Aufschlüsselung
   akzeptiert / bereits gelistet / Wiederholungen-in-der-Datei / ungültig,
   bevor Sie committen.
4. **Import bestätigen** — führt den committenden Schreibzugriff aus.
   Waren Zeilen ungültig, laden Sie **`skipped.csv`** herunter, um sie zu
   korrigieren und erneut zu importieren.

Der Assistent erzwingt außerdem die Dateityp- und 25-MB-Prüfungen
clientseitig, sodass ein falsch exportierter Typ ausfällt, bevor er die
API überhaupt erreicht.

***

## Die Unterdrückungsliste exportieren

`GET /compliance/suppression-list/export` lädt das
Unterdrückungsregister herunter — das symmetrische Gegenstück zum
[Import](#csv-massenimport) oben. Nutzen Sie es, um einem Regulator oder
Auditor zu belegen, welche Adressen zu einem gegebenen Zeitpunkt
unterdrückt waren, einschließlich massenimportierter Nummern ohne
passenden Kontakt.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/compliance/suppression-list/export?format=csv&status=active" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -o suppression-list.csv
```

Abfrageparameter:

| Parameter     | Typ     | Hinweise                                                                                             |
| ------------- | ------- | ---------------------------------------------------------------------------------------------------- |
| `format`      | enum    | `csv` (Standard) oder `json`.                                                                        |
| `channel`     | enum    | Auf einen Kanal-Geltungsbereich beschränken.                                                         |
| `status`      | enum    | `active` (Standard — die Menge, die jedes Send-Gate tatsächlich durchsetzt), `revoked` oder `all`.   |
| `from` / `to` | string  | Datumsbereich auf `suppressed_at`. Ein bloßes `YYYY-MM-DD`-Datum oder ein RFC-3339-Datums-Zeit-Wert. |
| `limit`       | integer | Einzuschließende Zeilen (1–50.000, Standard 50.000).                                                 |

Jede Zeile trägt `suppression_id`, `channel`, `address`, den
abgeleiteten `status` (`active` oder `revoked`), `reason`, `source`,
`contact_id` (leer bei massenimportierten Adressen ohne Kontakt),
`notes` und die Zeitstempel `suppressed_at` / `revoked_at` /
`created_at`.

Der Zugriff ist auf **Owner**- und **Admin**-Keys beschränkt, und jeder
Exportlauf wird ins Audit-Log geschrieben. Überschreitet das Register
50.000 Zeilen, trägt die CSV-Antwort einen
`X-Export-Truncated: true`-Header (das JSON-Äquivalent setzt
`truncated: true`) — verengen Sie nach Kanal oder exportieren Sie
aufeinanderfolgende Datumsfenster, um den Rest zu erfassen.

***

## Verifizieren, dass eine Unterdrückung gewirkt hat

Vertrauen ist gut, Kontrolle ist besser: Nach einem Import (oder jedem
Opt-out-Ereignis) bestätigen Sie, dass das Send-Gate die Adresse
tatsächlich einzäunt, bevor Sie die Liste einer Kampagne übergeben.

1. **Eine Testnachricht an die unterdrückte Adresse senden.** Ein
   direkter API-Versand an einen unterdrückten Empfänger schlägt
   synchron mit HTTP 422 und dem Fehlercode `RECIPIENT_OPTED_OUT` fehl.
   Im [Sandbox-Modus](/sandbox/magic-numbers) wird kein Carrier berührt
   und kein Guthaben abgebucht; jeder Empfänger, der auf `8` endet,
   löst außerdem in die simulierte `blocked`-Zustellquittung auf —
   die carrierseitige Sicht derselben Einzäunung.

   ```bash theme={null}
   curl -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
     -H "X-API-Key: $ORBIT_SANDBOX_KEY" \
     -H "Content-Type: application/json" \
     -d '{"to": "+14155550101", "from": "+15005550101", "body": "gate check"}'
   ```

   ```json theme={null}
   {
     "error": {
       "code": "RECIPIENT_OPTED_OUT",
       "message": "Recipient has opted out of this channel"
     }
   }
   ```

   Ein Kampagnenversand an dieselbe Adresse verhält sich bewusst
   anders: Der Empfänger wird still übersprungen
   (`status: "skipped"`, `reason: "opted_out"`), damit der Batch
   weiterläuft — prüfen Sie den Empfängerbericht der Kampagne, statt
   einen Fehler zu erwarten.
2. **Bestätigen, dass der Eintrag auf dem Register steht.** Exportieren
   Sie mit [der Abfrage oben](#die-unterdrückungsliste-exportieren)
   (`status=active` ist der Standard) und prüfen Sie, dass die Adresse
   mit dem erwarteten `channel`-Geltungsbereich erscheint. Der Export
   ist die Wahrheitsquelle, die jedes Send-Gate liest — ist die Zeile
   dort `active`, steht die Einzäunung.

Die beiden Prüfungen beantworten verschiedene Fragen: Schritt 1 beweist
Durchsetzung (das Gate feuert), Schritt 2 beweist Geltungsbereich (der
Eintrag existiert mit dem von Ihnen beabsichtigten Kanal).

***

## Eine Unterdrückung entfernen (Re-Opt-in)

Um eine Adresse zurückzuholen, erfassen Sie ein frisches Opt-in über
die [Consent API](/compliance/consent-management) (`opt_in: true`).
Das widerruft den passenden Unterdrückungseintrag und räumt die
STOP-Einzäunung weg. Schreiben Sie einen zuvor unterdrückten Kontakt
niemals ohne ein dokumentiertes, frisches Einwilligungsereignis
wieder an.

**Bestätigen, dass die Einzäunung weg ist.** Exportieren Sie mit
`status=revoked` und finden Sie die Adresse: Die Zeile ist für Audits
weiter vorhanden mit `status: revoked` und einem `revoked_at`-Zeitstempel —
Unterdrückungshistorie wird nie gelöscht, nur widerrufen. Senden Sie
dann eine kleine Testnachricht an die Adresse wie unter [Verifizieren,
dass eine Unterdrückung gewirkt
hat](#verifizieren-dass-eine-unterdrückung-gewirkt-hat): Eine
erfolgreiche Einreichung (kein `RECIPIENT_OPTED_OUT`) bestätigt, dass
das Gate auf widerrufener Historie nicht mehr feuert. Bis beide
Prüfungen bestehen, behandeln Sie die Adresse als weiter eingeäunt.

***

## Weiterführende Referenzen

* [Consent Management](/compliance/consent-management) — Einwilligung je
  Kanal erfassen und abfragen.
* [Send Gates](/compliance/send-gates) — Ruhezeiten, DNC, RND, RMD,
  Notfall-Stopp und das Preference-Center.
* [DSAR](/compliance/dsar) — wie `delete`- / `opt_out`-Anfragen zur
  Unterdrückung gelangen.
* [API Reference → Opt-outs](/api-reference/optouts) — Endpunktschemata
  für Opt-out und Unterdrückung.
