Worked request and response samples
1) Create a custom opt-out list
One call equips a brand’s keyword aliases and auto-reply copy. The French opt-out vocabulary is the worked example — adding ARRET alongside the platform-mandated STOP, per-keyword auto-reply copy, and a brand tag so the reply text feels local to the audience:
POST /api/v1/messaging/opt-out-lists
The returned id is what a messaging service stores in its opt_out_list_id. Per-keyword arrays stay under the caps: 32 entries per keyword kind, 64 chars per keyword, 1600 chars per auto-reply.
2) Read the list back — arrays are additive, never narrowing
A GET shows exactly what the list adds. The tenant-owned arrays only WIDEN the trigger surface on top of the platform-mandated STOP / HELP / START sets — tenant lists can never remove or replace them. TCPA and CTIA require the mandatory keywords to stay live, and the matcher enforces that at the platform level, whatever any list configures.
GET /api/v1/messaging/opt-out-lists/{id}
3) Attach the list to a messaging service
Until it is attached, a list has no effect — point the messaging service’s opt_out_list_id at the id saved from step 1 (the same attach call outlined on the messaging credentials page):
PATCH /api/v1/messaging/services/{id}
After attachment, every inbound keyword match through that service merges this list’s arrays with the platform defaults. Detach by setting "opt_out_list_id": null; the service falls back to the platform-mandated keywords alone.
4) Update aliases — replace the array with the full intended set
Keyword arrays replace wholesale on PATCH, so adding an alias means resending the existing entries plus the new one. The response echoes the complete new set:
PATCH /api/v1/messaging/opt-out-lists/{id}
The list is a tenant-owned, additive-only control. You widen the trigger surface (above, "ANNULER" joins the platform-mandated STOP). Dropping every custom alias still leaves the platform set intact and matching. You cannot narrow the mandatory sets — TCPA / CTIA require them to stay live, and the keyword matcher enforces that for any tenant configuration. Follow the guide at guides/opt-out-lists; do not build client logic that tries to subtract keywords.
Errors worth branching on
These failures cover the list lifecycle plus the opt-out add (POST /api/v1/opt-out-lists//entries), hammered from inbound STOP handlers. Each block below is a full { error, meta } envelope as the API returns it, and the matrix at the bottom answers retry vs surface for each class. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
A 401 on this page is the bearer key failing before the route ran — it never means the resource is wrong. Rotate the key or re-mint the scoped token; retrying the same request changes nothing.
403 — Forbidden
A 403 means the key authenticated but the operation is gated by scope — check the key’s scopes on the developer page; a 403 is never a data-not-found shape.
422 — Schema
A 422 means the payload did not match the request schema — branch on error.details.field and resend with the recipient in E.164 form instead of blind-retrying the same body.
429 — Rate
The Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two.
409 — Conflict
Treat as a no-op — the suppression already took; do not surface it as a send-stopper.
409 — Label conflict
A second shape of 409, distinct from the entry-add conflict above: POST (or PATCH with a new label) names a label an active list in this tenant already uses. Labels are unique among active lists, and soft-deleting a list frees its label for reuse.
Pick a distinct label (or rename the conflicting list) and resend — a blind retry repeats the same 409.
60-second retry matrix