Worked request and response samples
A messaging service is the container/v1/messages resolves once at send time — sender pool, opt-out list, inbound webhook, sending flags and throughput cap under one id you pass as messaging_service_id. Only label is required at create; every other field is nullable. The four-step loop below is the whole journey: make the container, bind two senders, verify the residency pin, then check the per-service throughput gate before arming an outbound campaign. Channel-fallback equivalents of this exact loop are in SMS services and the Messaging Services console guide.
1. Create the container
POST /api/v1/messaging/servicesnull on a later PATCH clears them. external_id is only for migrator import (Twilio MGxxx) and must be unique — a duplicate returns 409 (see the 409 block below).
2. Bind two senders
POST /api/v1/messaging/services/{id}/phone-numbersDELETE /api/v1/messaging/services/{id}/phone-numbers/{numberId} (204, no body).
3. Verify the residency pin
Before arming a campaign for a regulated buyer, confirm where the message plane rests and is processed.GET /api/v1/messaging/data-residencystoredAndProcessedInRegion: true is the whole answer — both the at-rest and the processing axes of the plane honour the pin. When it reports false, route the campaign through BYOC or hold it. This is a tenant-owned control; it wires no carrier and changes nothing about how traffic terminates.
4. Check the throughput gate, then send
Before arming a campaign, read the per-service gate live and re-arm only when the queue is clear.GET /api/v1/messaging/services/{id}/throughput-statsmps_cap is null when the service is uncapped; queue_depth counts over-cap sends 429’d this second. Raise or clear the cap with PATCH /api/v1/messaging/services/{id}/throughput ({ "mps_cap": 80 } or null). The frequency caps guide covers recipient-level pacing; message suppression handles opt-outs — both compose into the same gate.
These five failures cover the messaging-service create (POST /api/v1/messaging-services), which campaign provisioning flows through. 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 the same five classes. For the platform-wide decision table these branches plug into, see the error handling guide.
401 — Unauthorized
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
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
422 means the payload did not match the request schema — branch on error.details.field and resend with an allowed service name (1-48 chars) instead of blind-retrying the same body.
429 — Rate
Retry-After header and error.details.retry_after are both set on every 429 — resend the SAME request after the lower of the two.