> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 10DLC registration wizard with save-and-resume drafts

> Register your TCR brand and campaign through the Orbit wizard: save partial drafts across sessions, verify a sole-proprietor phone by OTP, preflight the saved draft, and submit atomically.

# 10DLC Registration Wizard

The wizard is the recommended way to complete [TCR brand and campaign registration](/guides/10dlc-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.

<Note>
  The wizard writes to your organization's own draft storage only. It does not hold, gate, or route any message traffic.
</Note>

All wizard endpoints live under `/api/v1/compliance/10dlc/wizard`. Authenticate with your API key, exactly as for the [brand and campaign endpoints](/guides/10dlc-registration).

## 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"`.

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

**Response:**

```json theme={null}
{
  "data": {
    "state": "in_progress",
    "current_step": "campaign",
    "brand": {
      "completed_fields": 10,
      "total_fields": 10,
      "missing_fields": []
    },
    "campaign": {
      "completed_fields": 4,
      "total_fields": 6,
      "missing_fields": ["message_flow", "optout_message"]
    },
    "next_action": "fill_campaign"
  },
  "meta": {
    "request_id": "req_wiz001",
    "timestamp": "2026-09-02T10:00:00Z"
  }
}
```

`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.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/draft \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "brand": {
      "entity_type": "PRIVATE_PROFIT",
      "display_name": "Acme Corp",
      "company_name": "Acme Corporation Inc.",
      "email": "compliance@acme.com"
    },
    "current_step": "brand"
  }'
```

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`.

<Tip>
  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.
</Tip>

***

## 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:

```bash theme={null}
# 1. Dispatch the challenge to the draft's brand.phone
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/phone/send \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

**Response:**

```json theme={null}
{
  "data": {
    "verification_id": "vf_abc123",
    "status": "pending",
    "channel": "sms",
    "expires_at": "2026-09-02T10:10:00Z"
  },
  "meta": {
    "request_id": "req_wiz002",
    "timestamp": "2026-09-02T10:00:00Z"
  }
}
```

```bash theme={null}
# 2. Confirm the code from the SMS
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/phone/confirm \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"code": "483921"}'
```

**Response:**

```json theme={null}
{
  "data": {
    "status": "verified",
    "phone": "+14155551234",
    "verified_at": "2026-09-02T10:04:12Z"
  },
  "meta": {
    "request_id": "req_wiz003",
    "timestamp": "2026-09-02T10:04:12Z"
  }
}
```

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](/guides/10dlc-registration#preflight-your-submission) 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.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/preflight \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The response shape — `score`, `verdict`, `findings[]` — is identical to the [preflight linter on the registration page](/guides/10dlc-registration#what-the-10dlc-linter-checks). 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.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/submit \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

**Response (`201 Created`):**

```json theme={null}
{
  "data": {
    "state": "brand_pending",
    "current_step": "review",
    "brand_id": "BXXXXXX",
    "campaign_id": "CXXXXXX",
    "next_action": "done"
  },
  "meta": {
    "request_id": "req_wiz004",
    "timestamp": "2026-09-02T10:05:00Z"
  }
}
```

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.

***

## DELETE `/10dlc/wizard/draft` — reset

```bash theme={null}
curl -X DELETE https://api.orbit.devotel.io/api/v1/compliance/10dlc/wizard/draft \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

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](/guides/10dlc-registration#step-3-wait-for-approval)).
6. `DELETE /10dlc/wizard/draft` — optional post-approval cleanup.

<Warning>
  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.
</Warning>
