Skip to main content

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, and review as 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/draft persists 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_id in from the brand outcome; you never type it.
All wizard endpoints live under /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".
Response:
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.
A save that fails validation returns 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.
Save after each required field if you like — each save costs under one write-rate token and the draft is the safety net. The campaign’s brand_id is never typed by hand: the wizard fills it in from the brand submission outcome.

Per-field validation rules

A PUT /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 with entity_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:
Response:
Response:
The proof is keyed to the exact phone string saved on the draft. If you edit 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.
The response shape — 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.
Response (201 Created):
Validation failures return 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_action flips to amend_brand. Amend the brand fields and resubmit.
  • Campaign rejected upstream — the accepted brand_id is persisted, state stays at brand_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.
State transitions to 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:
  • title and meaning — 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 example brand.ein or campaign.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 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 of GET /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

Returns 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

  1. PUT /10dlc/wizard/draft — progressively fill brand + campaign.
  2. (SOLE_PROPRIETOR only) POST /10dlc/wizard/phone/send → POST /10dlc/wizard/phone/confirm.
  3. POST /10dlc/wizard/preflight — fix findings until the verdict passes.
  4. POST /10dlc/wizard/submit — atomic submission.
  5. GET /10dlc/wizard — poll state until ready (or GET /10dlc/campaigns/:id/status for the per-carrier map, as on the registration page).
  6. DELETE /10dlc/wizard/draft — optional post-approval cleanup.
The legacy single-shot endpoints (POST /10dlc/brand, POST /10dlc/campaign) stay available for scripted pipelines. The wizard is the recommended operator flow; the direct endpoints demand a fully populated payload in one request.

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.