Skip to main content

Troubleshooting: opt-out list label conflicts

POST /api/v1/messaging/opt-out-lists and PATCH /api/v1/messaging/opt-out-lists/:id enforce that list labels are unique among active lists in your tenant. When the label you sent belongs to a list that is currently active, the write fails with:
The gate is deterministic — nothing is created, renamed, or billed — so a blind retry returns the same 409 every time. Resolve the label collision first, then re-send once.

When the gate fires

Two write surfaces raise OPT_OUT_LIST_LABEL_CONFLICT, and both collapse to one rule: the label you sent is in use by an active list. Distinguish this from the sibling codes on the same endpoints before you act:
  • 403 FORBIDDEN — your API key lacks the scope for opt-out-list writes. Fix the key’s scope set; it never heals on retry.
  • 409 OPT_OUT_LIST_LABEL_CONFLICT — the label duplicates an active list (this page).
  • 422 VALIDATION_ERROR — the payload is malformed: labels over 200 chars, keyword arrays over 32 entries (each 1–64 chars), response copy over 1600 chars, or a default_lang outside the supported set (en, tr, de, es, fr, pt, it, ar, nl). A soft-deleted list’s label is free to reuse — only active lists participate in the check — so if the conflicting list was deleted, the label is available again.

The correct path: resolve, then re-send once

Pick ONE of the three resolutions — each is a deliberate write, not a retry:

1. Locate the conflicting list

List your active opt-out lists (soft-deleted rows are excluded by default) and find the row that owns the label:
To also review soft-deleted rows — for example, to confirm whether the label is genuinely free — pass include_deleted=true and inspect the deleted flag on each entry:
The list is keyset-paginated; follow meta.pagination.next_cursor until you find the row.

2. Choose a resolution

  • Rename your new list — the most common path. Re-send the POST with a distinct label. Labels are display names, so suffixing by brand, region, or campaign (Acme FR — 2026Q3) keeps them unique.
  • Rename the existing list — PATCH /api/v1/messaging/opt-out-lists/:id on the conflicting list with a new label, then create yours. Do this when the existing list’s name was the collision source (e.g. a renamed brand).
  • Retire the existing list — if the conflicting list is genuinely unused, DELETE /api/v1/messaging/opt-out-lists/:id soft-deletes it. Soft-delete frees the label for reuse, and historic messaging services that referenced the list keep resolving it.
A soft-deleted list is never the blocker: the uniqueness check covers active lists only, so a deleted: true row cannot produce this 409.

Do not treat PATCH as an upsert

PATCH and POST have different contracts — confusing them is the second most-common cause of a repeated 409:
  • POST /api/v1/messaging/opt-out-lists always creates a new list. It is not an upsert: if the label exists on an active list, the write fails. To change a list you already have, PATCH it by id.
  • PATCH /api/v1/messaging/opt-out-lists/:id updates only the fields you send. Keyword arrays included in the body fully replace that kind (stop_keywords, help_keywords, start_keywords); an empty array clears that kind. Fields you omit are untouched. label is optional in the patch — the 409 only appears when the patch actually renames the list into a collision.
Also note: per-list keyword arrays widen the trigger surface — they add to the carrier-mandated STOP/HELP/START defaults, they never narrow them. You cannot remove STOP; TCPA and CTIA require it.

If it still refuses

  1. Read error.message — it quotes the colliding label verbatim.
  2. Confirm the resolution you chose actually landed (list active lists again).
  3. If the label still conflicts and you cannot see an active list that owns it, open a ticket with meta.request_id from the refused response.
For the full error-shape reference, see OPT_OUT_LIST_LABEL_CONFLICT and the endpoint-level retry matrix in the opt-out lists endpoint reference.