Skip to main content

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 is https://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
Treat 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 returns not_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
Lint findings name the field, the offending wording, and a suggested rewrite — fix them, re-lint, and only then file the brand:
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). 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
Branch on 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
The branch table — 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
Both rejections are per-recipient business outcomes, not provider faults: 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 the metadata 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
Keep the distinction honest — the gates treat the two lanes differently by design, and a mis-tagged send either blocks legitimate transactional traffic or (worse) exempts marketing from the quiet-hours gate: 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