Skip to main content

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/services
Nullable fields you omit stay unset; a null 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-numbers
Run this twice with two different DIDs so the pool has redundancy; reattach of the same pair is idempotent (same envelope, no error). The DID must belong to the tenant and be SMS-capable — a voice/fax/MMS-only DID is rejected with 422. Detach later via DELETE /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-residency
storedAndProcessedInRegion: 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-stats
mps_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

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 an allowed service name (1-48 chars) 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

Detach via the existing service first, then POST again — the sender mapping is single-owner.

60-second retry matrix