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 raiseOPT_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 adefault_langoutside 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:include_deleted=true and inspect the deleted
flag on each entry:
meta.pagination.next_cursor until you
find the row.
2. Choose a resolution
- Rename your new list — the most common path. Re-send the
POSTwith a distinctlabel. 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/:idon the conflicting list with a newlabel, 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/:idsoft-deletes it. Soft-delete frees the label for reuse, and historic messaging services that referenced the list keep resolving it.
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-listsalways 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,PATCHit by id.PATCH /api/v1/messaging/opt-out-lists/:idupdates 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.labelis optional in the patch — the 409 only appears when the patch actually renames the list into a collision.
STOP; TCPA and CTIA require it.
If it still refuses
- Read
error.message— it quotes the colliding label verbatim. - Confirm the resolution you chose actually landed (list active lists again).
- If the label still conflicts and you cannot see an active list that owns
it, open a ticket with
meta.request_idfrom the refused response.