10DLC Registration Wizard
The wizard is the recommended way to complete TCR brand and campaign registration. It turns the linear single-shot flow into a guided, resumable process:- Save and resume — your draft persists across sessions, tabs, and weeks. Close the tab or come back tomorrow; you pick up where you left off.
- Move between steps non-linearly — the wizard tracks
brand,campaign, andreviewas independent steps. Fill the campaign samples while the brand section is still incomplete, and the progress payload keeps both counts. - Validate before you pay — per-field validation fires on every save, and a pre-submission lint pass scores the saved draft against known TCR rejection patterns before the upstream registration fee is charged.
The wizard writes to your organization’s own draft storage only. It does not hold, gate, or route any message traffic.
Why the wizard exists
The registry’s own brand and campaign flow asks for roughly 25 fields across two dense forms, validates them only after you submit, and saves nothing along the way. The wizard exists to remove the four failure modes operators hit most often in that flow:- Lost work — the linear flow has no save-as-you-go; closing a tab discards every keystroke. Here, each
PUT /wizard/draftpersists the draft, so a closed tab or a week away costs nothing. - Re-entered work after a rejection — brand vetting takes 1–3 business days, and in the linear flow a brand rejection means re-typing the campaign you were preparing in parallel. Here, the campaign draft survives a brand rejection.
- Validation after payment — the linear flow discovers a malformed EIN or a too-short sample message one paid retry cycle later. The wizard validates each field on every save and lints the saved draft before any registration fee is charged.
- An invisible dependency — a campaign submission needs the brand id minted by the brand submission. The wizard fills
brand_idin from the brand outcome; you never type it.
/api/v1/compliance/10dlc/wizard. Authenticate with your API key, exactly as for the brand and campaign endpoints.
Roles and rate limits
- Reads (
GET /wizard,GET /wizard/draft) — any authenticated role on the organization. - Writes (
PUT /wizard/draft,DELETE /wizard/draft,POST /wizard/phone/send,POST /wizard/phone/confirm,POST /wizard/preflight,POST /wizard/submit) — owner or admin role. - Write profile: 10 requests per minute, matching the legacy brand/campaign endpoints. Wizard preflight: 30 requests per minute, matching the ad-hoc preflight endpoint.
GET /10dlc/wizard — progress payload
Returns the computed progress object the dashboard uses for its “Continue where you left off” banner. Always 200 OK — a fresh organization gets the empty-draft shape with state: "not_started".
state is one of not_started, in_progress, brand_pending, campaign_pending, ready, rejected. current_step is brand, campaign, or review. next_action is a machine-readable hint: fill_brand, fill_campaign, review_and_submit, amend_brand, amend_campaign, or done. When the brand or campaign has been submitted, the response also carries brand_id, campaign_id, and any upstream rejection reason.
GET /10dlc/wizard/draft returns the full saved draft (brand fields, campaign fields, current step) for form prefill.
PUT /10dlc/wizard/draft — partial save
Saves any subset of brand and/or campaign fields. Every field is optional — you only validate the fields you send, and the rest keep their previously saved values. current_step updates separately so a resume lands on the right screen.
422 and leaves the persisted draft untouched — the previously saved state survives.
Brand fields: entity_type, display_name, company_name, ein, phone, street, city, state, postal_code, country, email, website, vertical.
Campaign fields: usecase, description (40–4096 characters), sample_message (1–10 messages), message_flow (min 40 characters), help_message (min 20), optout_message (min 20), is_political, cv_token.
Per-field validation rules
APUT /wizard/draft validates only the fields you send and stores nothing on failure, so you can probe one field at a time. The rules:
Empty strings and whitespace-only strings are dropped on save rather than stored, so the progress counts on
GET /wizard always reflect real field values.
Sole-proprietor phone verification (OTP)
US brands withentity_type: "SOLE_PROPRIETOR", and only those, substitute a verified mobile number for an EIN: TCR anchors sole-proprietor identity on a phone number the registrant proves they control. Every other US entity type must submit an EIN instead.
Verify the number before submitting:
brand.phone after verifying, the old proof no longer counts and submit fails with 422 until you run a fresh challenge against the new number. Calling /phone/send on an already-verified number returns 409 ALREADY_VERIFIED; calling /phone/confirm without a pending challenge returns 404.
POST /10dlc/wizard/preflight — lint the saved draft
Scores the persisted wizard draft against the known TCR rejection-pattern catalog, so the “Review & submit” screen sees findings against the exact draft it is about to submit — no chance of lint drift that the ad-hoc preflight endpoint has when it lints a hand-built payload.
The request body is optional: expected_msg_per_day_per_number (for the throughput-tier rule) and brand_vetting_score (0–100) when you already hold one.
score, verdict, findings[] — is identical to the preflight linter on the registration page. As with that endpoint, a pass verdict means “no known rejection patterns matched,” not “TCR will approve.”
POST /10dlc/wizard/submit — atomic brand + campaign
Validates the full draft, then submits the brand and the campaign in one call. Brand vetting typically lands in 1–48 hours; the campaign follows immediately after.
201 Created):
422 before anything is charged, with details.section telling you whether the missing or invalid fields are in the brand or campaign section.
Failure semantics — the atomic guard:
- Brand rejected upstream — no campaign is submitted. The draft is preserved with the brand rejection reason stamped, and
next_actionflips toamend_brand. Amend the brand fields and resubmit. - Campaign rejected upstream — the accepted
brand_idis persisted, state stays atbrand_pending, and the campaign rejection reason is stamped. A resubmission skips the brand stage entirely, so the brand fee is never re-charged; only the campaign pays again. - Upstream 5xx or provider unavailable — the draft is preserved verbatim and you can retry as-is.
ready once both the brand and campaign come back APPROVED from carrier review.
Reading a rejected submission
When a submit is rejected, the wizard stamps the rejection on the draft:GET /wizard then returns state: "rejected" with next_action: "amend_brand" or "amend_campaign" and a brand_rejection_reason or campaign_rejection_reason, and GET /wizard/draft carries the same reason on the affected section. The rejection banner on the review step renders a fix card decoded from that reason.
Rows the registry and the CSPs return are terse by design: a numeric code like 40016 or a hyphenated slug like EIN-MISMATCH, plus one free-text sentence, and no per-field guidance. Decoding that row by hand is slow and error-prone: operators paste the code into a search engine, wade through stale forum answers, and pay a fresh vetting fee plus 1–5 business days on the next attempt. Orbit ships a decoder catalog so you never have to do that. Every rejection reason that reaches the wizard is normalized to a fix card, in the dashboard banner and from POST /api/v1/compliance/10dlc/decode-rejection, whose full walkthrough lives in 10DLC rejections and re-vetting. Each card carries:
titleandmeaning— what the registry or carrier actually rejected, in plain English.fix— exactly one concrete corrective step, the highest-probability one first. Not a list of maybes.resubmit_allowed— the verdict that decides your next call.true: amend and resubmit the same record.false: the record is locked and the fix is a fresh registration, described below.category— one of the stable buckets in the table below, for grouping rejections in your own tooling.field— the wizard step the fix applies to (for examplebrand.einorcampaign.sample_message), which is what the banner uses to link you straight back to the field to correct.references— public registry and carrier documentation, when one applies.
The decoder catalog
resubmit_allowed: amend versus start over
Two codes lock the record instead of asking for an amendment. BRAND-DCA-FAIL means a carrier ran a direct audit after registry approval and rejected the brand; the brand id is burned and the fix is a fresh brand registration, not an edit. DUPLICATE-BRAND means the registry already holds a brand for that EIN and display-name pair; the fix is reusing the existing brand id, and resubmitting would pay a second fee for an orphan record.
Every other card lets you amend the pointed-at field and resubmit the same record. When the campaign was the rejected stage, resubmission reuses the already-approved brand id, so the brand fee is never paid twice. The full re-vet-versus-resubmit cost tradeoff, with worked examples, is in 10DLC rejections and re-vetting.
The same cards reach you through POST /api/v1/compliance/10dlc/decode-rejection if you want to decode a raw code yourself, and the dashboard renders the card for any stamped rejection without an extra call.
field: the deep link back into the wizard
field names the exact draft field the fix applies to, as brand.<field> or campaign.<field>. The dashboard’s rejection banner uses it to place a link directly on that field, so applying the fix is a one-click jump into the right step rather than a hunt through two forms.
When a code is not in the catalog
The decoder never returns an empty card. A reason it cannot normalize still yields a full card:category unknown, a short explanation, and a fix that routes the raw rejection text to Devotel compliance support, with new codes added to the catalog as they appear. You always get a normalized card to act on, never a blank fix panel.
Code spelling is normalized before lookup, so TCR-30883, Twilio Error 30883, and ein_mismatch all resolve to the same cards as 30883 and EIN-MISMATCH. When the code alone is ambiguous, the registry’s free-text reason is inspected to pick the matching card.
The wizard state machine
GET /wizard reports exactly one state; writes only ever move it forward, and DELETE /wizard/draft is the single hatch back to the start.
A draft save never moves the state backward out of
brand_pending, campaign_pending, or ready; only the submit path, the approval webhooks, and DELETE /wizard/draft change it. Inside in_progress, current_step (brand, campaign, or review) tracks where you are, and next_action tells your tooling what to do next: fill_brand, fill_campaign, review_and_submit, amend_brand, amend_campaign, or done.
Resume: “Continue where you left off”
The dashboard banner is a plain rendering ofGET /wizard: state picks the banner, current_step picks the screen to land on, the brand and campaign counts drive the progress ring, and next_action sets the call-to-action. Because the draft is your organization’s persisted storage, “resume” works across sessions, browsers, and devices: any authenticated teammate sees the same banner, and GET /wizard/draft prefills the forms exactly as they were saved.
DELETE /10dlc/wizard/draft — reset
204 No Content. Resets the wizard to the empty draft (state: "not_started"). This does not clear the approved brand or campaign ids on file — it only resets the wizard working state, so it is safe as post-approval cleanup or a “start over.”
Full lifecycle order
PUT /10dlc/wizard/draft— progressively fill brand + campaign.(SOLE_PROPRIETOR only)POST /10dlc/wizard/phone/send→POST /10dlc/wizard/phone/confirm.POST /10dlc/wizard/preflight— fix findings until the verdict passes.POST /10dlc/wizard/submit— atomic submission.GET /10dlc/wizard— poll state untilready(orGET /10dlc/campaigns/:id/statusfor the per-carrier map, as on the registration page).DELETE /10dlc/wizard/draft— optional post-approval cleanup.
See also
- 10DLC registration — the happy path: brand, campaign, approval, plus the use-case and throughput-tier reference tables.
- 10DLC rejections and re-vetting — the post-rejection playbook: decoder endpoint walkthrough with worked examples, re-vet versus resubmit, and the cost of each retry.
- 10DLC preflight linter — the finding codes the pre-submission lint can return, and what each one asks you to change before you pay.