> ## 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 rejections, re-vetting, and the post-approval lifecycle

> Decode a raw TCR or carrier rejection code into a fix card, resubmit corrected brand and campaign payloads, request a brand re-vet when vetting score is the blocker, and read the AT&T throughput class and T-Mobile daily-cap tier your brand actually holds.

# 10DLC rejections, re-vetting, and the post-approval lifecycle

The [10DLC registration guide](/guides/10dlc-registration) ends at
"start sending." This guide covers what happens next — a rejection with a
cryptic code, a vetting score that caps your throughput, or an approved
campaign whose daily cap no longer fits your volume. Everything here is a
control on your own registrations; carrier and registry decisions remain
theirs.

## Where rejections come from

A rejection is one of two things, and the difference decides everything
you do next:

* **Registry rejections (TCR and the CSPs that proxy it)** fire during or
  after registration review — the filing itself is refused. These surface
  as `FAILED` / `REJECTED` status records with a `rejectionReason`
  string, in three places: the **Settings > Compliance > 10DLC** wizard,
  a dashboard notification on the terminal transition, and the
  `rejectionReason` field on
  `GET /api/v1/compliance/10dlc/campaigns/:id/status`.
* **Carrier rejections (AT\&T, T-Mobile)** can land after TCR approval,
  inside the per-carrier `mnoStatuses` map — a direct carrier audit on
  the brand, or one carrier disagreeing with the CSP-level decision.

Some codes are emitted **before** TCR decides anything: `DUPLICATE-BRAND`
and the lowercase-`usecase` failure are submission-time signals, not
review-phase outcomes. A rejection the wizard shows immediately after you
submit usually falls in this class.

Two rejections lock the record instead of requesting an amendment —
`BRAND-DCA-FAIL` and `DUPLICATE-BRAND` return a fix card with
`resubmit_allowed: false`, meaning the brand id is burned and the fix is
a fresh registration, not an edit.

## Decode a raw rejection code into a fix card

TCR and the CSPs return codes like `30883`, `40016`, and
`EIN-MISMATCH` with a one-line free-text reason and no per-field fix.
The remediation table on
[Troubleshooting: 10DLC campaign rejected](/troubleshooting/10dlc-campaign-rejection)
covers the common cases statically; the decoder endpoint applies the same
catalog to any code — including wording and codes the table does not
list.

`POST /api/v1/compliance/10dlc/decode-rejection`

Pass the raw `code`, plus the free-text `rejectionReason` when you have
it — the free text disambiguates codes that map to more than one cause:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/decode-rejection \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "30883",
    "free_text": "Sample messages include prohibited SHAFT content"
  }'
```

**Response (`200 OK`):**

```json theme={null}
{
  "data": {
    "code": "30883",
    "title": "Content violation",
    "meaning": "Carrier (typically T-Mobile) determined the sample messages or use case describe content prohibited under 10DLC policy — SHAFT-C, gambling-without-license, loans without a registered FCRA disclosure, or messaging unrelated to the declared vertical.",
    "fix": "Rewrite sample messages to match the declared use case verbatim; remove SHAFT-C wording, shortener domains, and generic 'click here' CTAs. Re-state the opt-in moment in message_flow.",
    "resubmit_allowed": true,
    "category": "content",
    "field": "campaign.sample_message",
    "references": [
      "https://support.twilio.com/hc/en-us/articles/4424803542043",
      "https://www.csptcr.com/wp-content/uploads/2023/03/Campaign-Registry-Common-Rejections.pdf"
    ]
  },
  "meta": {
    "request_id": "req_dec001",
    "timestamp": "2026-09-01T09:00:00Z"
  }
}
```

Read four fields off the card:

* **`fix`** — the single highest-probability corrective step, in the same
  register the wizard's rejection banner uses.
* **`resubmit_allowed`** — the verdict that decides your next call.
  `true`: amend the payload and resubmit the same brand or campaign.
  `false`: the record is locked; start a fresh registration.
* **`category`** — a stable bucket (`eligibility`, `content`, `identity`,
  `throughput`, `format`, `dca`, `unknown`) for grouping in your own UI.
* **`field`** — the wizard step the fix applies to
  (`campaign.sample_message`, `brand.ein`, …), so you can deep-link the
  operator back to the right form.

The decoder is a pure rule engine — it stores nothing, creates nothing,
and normalizes the code for you: `TCR-30883`, `Twilio Error 30883`, and
`ein_mismatch` all resolve to their canonical entries. An unrecognized
code returns an `unknown`-category card whose `fix` routes you to
Devotel compliance, so a UI that renders the card never shows an empty
panel.

<Tip>
  Run the decoder before you run remediation. When the card's `fix`
  rewrites a sample message or description, run the corrected payload
  through the
  [preflight linter](/guides/10dlc-registration#preflight-your-submission)
  before resubmitting — it catches the second-order rejection the fix
  often introduces.
</Tip>

## Worked example: 'use case mismatch'

A campaign status poll returns:

```json theme={null}
{
  "data": {
    "campaignId": "C9X27A",
    "status": "FAILED",
    "rejectionReason": "use case mismatch — samples describe promotional traffic filed under CUSTOMER_CARE"
  }
}
```

The reason string names the cause but not a TCR code. The decoder works
with one or the other, so pass what you have — the free text alone:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/decode-rejection \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code": "unknown", "free_text": "use case mismatch"}'
```

**Response — the `30898` card:**

```json theme={null}
{
  "data": {
    "code": "30898",
    "title": "Use case mismatch with declared vertical",
    "fix": "Either change the brand's vertical to match the actual messaging, or pick a different use case that aligns with the brand identity (MARKETING / MIXED for promotional content).",
    "resubmit_allowed": true,
    "category": "eligibility",
    "field": "campaign.usecase"
  }
}
```

`resubmit_allowed: true` says amend-and-resubmit. Now resubmit the
corrected campaign against the same brand — the same call as the initial
filing, re-filed like-for-like:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/campaign \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brand_id": "B4D2E1",
    "usecase": "MARKETING",
    "description": "Acme sends weekend promotional offers to customers who opted in at checkout.",
    "sample_message": [
      "Acme: 20% off today at https://acme.com/sale. Reply STOP to unsubscribe."
    ],
    "message_flow": "Customers opt in at checkout and tick the SMS consent checkbox. Reply STOP to opt out.",
    "help_message": "Reply HELP for assistance or email support@acme.com.",
    "optout_message": "You have been unsubscribed and will receive no further messages."
  }'
```

Expect the same 1–5 business days as the initial filing; poll the status
endpoint or watch the dashboard notification.

## A second worked example: 'SHAFT sample'

This is the same loop with a content-class card. A reason like
`"Sample 2 flagged: SHAFT sample"` decodes to `30883` — content violation
— with `resubmit_allowed: true` and `field: "campaign.sample_message"`.
Rewrite the flagged sample to state the declared vertical and drop the
prohibited wording, then re-file the campaign like-for-like. The point of
the loop: one decoder call, one corrected submission, and none of it
depends on which wording TCR happened to return.

## Re-vet vs. re-submit

Rejected content fixes mean re-submission. A **low vetting score** is a
different blocker — no amount of correction to a campaign payload repairs
it, and it caps every campaign on the brand, not just the one under
review. The score decides the limit, so the remedy is a re-vet, not a
resubmission.

`POST /api/v1/compliance/10dlc/brands/:id/revet`

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/10dlc/brands/B4D2E1/revet \
  -H "X-API-Key: $ORBIT_API_KEY"
```

**Response (`200 OK`):**

```json theme={null}
{
  "data": {
    "brandId": "B4D2E1",
    "status": "PENDING",
    "provider": "telnyx"
  },
  "meta": {
    "request_id": "req_rev004",
    "timestamp": "2026-09-01T09:30:00Z"
  }
}
```

`provider` names which registrar carries the brand; `status` is the
refreshed brand status once the re-vet kicks off. Before you re-vet,
complete the brand record — EIN, legal name matched to IRS records,
website, support email on your own domain — the re-vet re-scores the same
information, and an unchanged record returns an unchanged score.

The registrar rate-limits re-vets (once immediately after registration,
then once per roughly three months), so a rate-limited call returns 422
with the registrar's wait-until text — read it, do not retry in a loop.

## Throughput planning: what the score grants

Two endpoints turn the vetting score into the limits you operate under.

`GET /api/v1/compliance/10dlc/brands/:id/vetting` returns the raw
result:

```json theme={null}
{
  "data": {
    "brandId": "B4D2E1",
    "provider": "telnyx",
    "vettingScore": 78,
    "vettingClass": "PASS",
    "vettedDate": "2026-08-30T12:00:00Z"
  }
}
```

`vettingScore: null` means the EVP is still processing — render
"Pending", not `0`.

`GET /api/v1/compliance/10dlc/brands/:id/throughput` derives the
carrier-assigned limits from that score:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/compliance/10dlc/brands/B4D2E1/throughput?entity_type=PRIVATE_PROFIT&sent_today=9800" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

**Response (`200 OK`):**

```json theme={null}
{
  "data": {
    "brandId": "B4D2E1",
    "provider": "telnyx",
    "vettingScore": 78,
    "vettingClass": "PASS",
    "vettedDate": "2026-08-30T12:00:00Z",
    "throughput": {
      "trust_score": 78,
      "provisional": false,
      "entity_type": "PRIVATE_PROFIT",
      "tmobile": {
        "tier": "HIGH",
        "daily_cap": 200000,
        "unlimited": false,
        "label": "High (200k message parts/day)"
      },
      "att": {
        "class": "A",
        "mpm": 600,
        "label": "Class A (600 MPM)"
      }
    },
    "consumption": {
      "daily_cap": 200000,
      "unlimited": false,
      "sent_today": 9800,
      "remaining": 190200,
      "percent_used": 5,
      "status": "ok"
    }
  }
}
```

* **`att.class`** — AT\&T assigns a per-campaign throughput class, D→C→B→A
  (15 → 75 → 240 → 600 messages per minute), at trust-score floors 0/25/50/75.
* **`tmobile.tier`** — T-Mobile assigns a per-brand daily-cap tier
  (2k / 10k / 40k / 200k / unlimited) that covers every campaign and
  number on the brand.
* **`entity_type`** (optional query param) — sole-proprietor brands are
  capped at the 2k tier and Class D regardless of score; pass the
  registered entity type so the derivation sees the special case.
* **`sent_today`** (optional query param) — message parts already sent in
  the current rolling 24h window. When you pass it, `consumption` reports
  headroom before the silent-filtering zone: `ok` below 80% of the cap,
  `warning` at 80%, `critical` at 95%, `exceeded` at 100%.

Both query params are optional; `consumption` is `null` until you supply
`sent_today`.

### Re-vet, or accept the tier?

Use the `warning` / `critical` thresholds as the signal. When your
foreground volume consistently lands in the warning band, plan a re-vet —
it is rate-limited, so burn it only on a real trajectory change. When the
ceiling is the daily cap rather than the class, the constraint is
per-brand: assigning more numbers to the campaign does not raise it, and
a `SOLE_PROPRIETOR` upgrade to a full brand record does — that is an
entity-type change, not a re-vet.

## Re-submission, re-vet: what each costs

TCR charges a new vetting fee per re-submission and per re-vet (typically
a few USD), and carriers treat every re-submission as a fresh review —
independent timing from your previous attempts.

## See also

* [10DLC registration guide](/guides/10dlc-registration) — brand →
  campaign → approval, use-case codes, and the throughput tier table
* [Troubleshooting: 10DLC campaign rejected](/troubleshooting/10dlc-campaign-rejection)
  — the static symptom → cause → fix matrix this guide's decoder applies
* [Pre-submit linter](/guides/10dlc-registration#preflight-your-submission)
  — run the corrected payload through preflight before resubmitting
