API recipes: the US 10DLC pre-send chain
The baseline compliance posture for US SMS traffic describes the gates a US SMS send passes — 10DLC registration state, opt-out suppression, quiet-hours, and the marketing/transactional lane split — but documents them conceptually with no runnable loop. These recipes assemble that chain over the API: the exact calls, the expected envelopes, and the branch you take on each outcome. Base URL ishttps://api.orbit.devotel.io/api/v1 throughout; brand and campaign writes require an owner/admin key, every read takes any authenticated key.
These are tenant-owned controls. Orbit supplies and enforces the gates; your organization files the registrations, seeds the suppression lists, and makes the go-live call. These recipes are not legal advice.
1. Pre-flight: read the sender’s registration state before you allow a send
A marketing send on an unregistered (or rejected) brand is not a send at all — carriers filter it downstream.GET /compliance/10dlc/status is the one read that answers “is this sender allowed to file traffic”:
cURL
200 returns the combined state machine:
200
data.overall_status as the send gate, and branch exactly once per send-batch:
Two finer branches matter before you block on
pending: read the per-item status on each brand and campaign (a rejected campaign returns its rejectionReason, which you decode with POST /compliance/10dlc/decode-rejection — see the rejections and re-vet guide), and match the usecase on the active campaign against the lane you intend to send. A MARKETING body on a CUSTOMER_CARE campaign is the classic use-case mismatch rejection from the baseline posture guide.
Canonical page: 10DLC registration.
2. Register the brand and campaign over the API
When recipe 1 returnsnot_registered, file the registration with two writes. Run the free pre-flight linter first — it scores the same fields TCR rejects on before any vetting fee moves:
cURL
cURL
201 returns { "brandId": "brnd_01HXV…", "status": "PENDING" }. Sole proprietors (entity_type: "SOLE_PROPRIETOR") omit ein and anchor on a verified phone number instead. With a brand id in hand, file the campaign the same way:
cURL
201 returns the campaign id with status: "PENDING". The decision table for the filing loop:
Both filings write the audit log (
compliance.10dlc_brand_registered, compliance.10dlc_campaign_registered).
Canonical pages: 10DLC registration, 10DLC registration wizard (the operator flow with save-and-resume).
3. Suppression pre-check: quiet-hours and consent before the send
Registration answers “can this brand send”; this pair answers “can this destination receive, right now.” Run both reads before dispatching a marketing send — the send path re-checks both and hard-rejects, so a pre-check turns a rejected send into a queued one. Quiet-hours preview — the same evaluator the send gate runs, read-only:cURL
200
data.allowed_now: false means schedule the send for data.next_allowed_at — never retry inside the window. Pass timezone_override when your CRM knows the recipient’s timezone; otherwise the NANP area code resolves it. Drop timezone_override and omit your own inference entirely — the preview mirrors the gate.
Consent and suppression lookup — the ledger entry every send gate reads:
cURL
200
marketing_eligible is false unless a live opted_in grant exists, so treat it as the send/no-send bit and use status for the why:
When a send does slip past your pre-check — a destination opted out between preview and dispatch — the send endpoint answers with a rejection envelope you branch on by
error.code:
422 — opted-out recipient
422 — inside quiet hours
RECIPIENT_OPTED_OUT is terminal for that destination (remove them from the campaign audience), QUIET_HOURS_BLOCKED means re-schedule at error.details.next_allowed_at, and QUIET_HOURS_TIMEZONE_UNKNOWN (the fail-closed sibling when no timezone can be resolved) means set the contact’s timezone or review your unknown_timezone_policy on the quiet-hours configuration page. Seeding a migrated suppression list is a one-time CSV import — POST /compliance/suppression-list/import — covered end to end in the suppression entry-point guide.
Canonical pages: Opt-Out & Suppression Lists, Quiet hours: org-wide channel gates.
4. Tag the lane: marketing versus transactional on the send body
Every gate above reads the same lane tag on the send itself. The send body carries it on themetadata envelope — traffic_lane always wins, message_type tags the marketing consent gates, and campaign-origin sends default to marketing when neither is set:
cURL — marketing promotion
cURL — transactional notice
A direct-API send with no lane metadata resolves to
transactional — tag marketing explicitly on every promotional send rather than relying on the campaign-origin default, so the intent survives a resend outside the campaign runner.
Canonical page: Baseline compliance posture for US SMS traffic.
See also
- Baseline compliance posture for US SMS traffic — the conceptual guide these four recipes make runnable
- 10DLC registration — brand and campaign filing, use-case codes, and the preflight linter
- 10DLC rejections and re-vet — decode a rejection, re-vet, read throughput tiers
- Opt-Out & Suppression Lists — the ledger the suppression pre-check reads
- Choose your suppression entry point — CSV import versus Consent API versus Preference Center
- Quiet hours: org-wide channel gates — per-channel windows, timezone resolution, the unknown-timezone policy
- API error handling by example — branch on
error.codefor every rejection shape above - API recipes: operations endpoints — the sibling cookbook for operational surfaces