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

# Short-code preflight: checks and remediation

> Score a short-code program brief against the carrier rejection-pattern catalog before you submit — every rule the preflight engine runs, what each verdict means, and the remediation for every failure code.

# Short-code preflight: checks and remediation

A short-code application goes through per-carrier vetting that runs for
weeks, and carriers reject roughly a third of first-round program briefs
on deterministic grounds — restricted content, ambiguous calls to
action, missing opt-out or disclosure wording. The preflight engine
scores your program brief against that known rejection-pattern catalog
**before** it enters the carrier queue, returning a score, a verdict,
and per-field findings with the exact offending fragment and a
concrete rewrite suggestion.

This page is the reference for that engine: the posture it runs under,
the endpoint surface, every rule it checks, the remediation for every
failure code, and the preflight → resubmission → carrier-verified
lifecycle. For the end-to-end submission walkthrough (opening the
application and tracking carrier vetting), see
[Apply for an SMS short code](/guides/short-code-application).

<Warning>
  This page describes a tenant-owned compliance control. It is **not
  legal advice.** Whether a program brief satisfies carrier and CTIA
  requirements is your program's responsibility — confirm with
  qualified counsel.
</Warning>

***

## Role and posture

The preflight follows the same posture as every Orbit compliance
control except the federal TCPA dialing window: **tenant-controlled,
default-open, advisory-only unless you enforce.**

* **Advisory, never a gate.** The endpoint only reads your brief and
  reports findings. It cannot block a submission, and it never touches
  sending — a `block` verdict is information for your submission
  decision, not an enforced stop. If your compliance policy requires a
  passing preflight before submission, enforce that in your own
  workflow.
* **Default-open.** No org toggle gates the endpoint; any authenticated
  caller with a read-capable role can lint a brief.
* **A clean pass is not carrier approval.** `pass` means "no known
  rejection pattern matched." The application still goes through the
  normal per-carrier vetting, which includes human review.

Because it is advisory, treat the preflight as the deterministic first
pass and carrier vetting as the authoritative second pass.

***

## Endpoint surface

Two endpoints run the same engine (`shortcode-preflight/v1`), and all
routes below are rooted at `https://api.orbit.devotel.io/api/v1`:

| Endpoint                                  | Body                                     | What it lints                                                          |
| ----------------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------- |
| `POST /numbers/short-codes/preflight`     | `programBrief` + optional `businessName` | An ad-hoc brief, before any application exists                         |
| `POST /numbers/short-codes/:id/preflight` | none                                     | The stored brief (and registrant business name) on a draft application |

### Field-by-field verification matrix

These are the request fields, their shape constraints (a violated
constraint returns `422 VALIDATION_ERROR` with per-field details), and
what the engine checks:

| Field                           | Shape                              | Engine checks                                                                                                       |
| ------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `businessName`                  | optional string, 1–200 chars       | Cross-checked against the samples to verify recipients can identify the sender                                      |
| `programBrief.useCase`          | string, 1–120 chars                | Carried through for the carriers; no rule scores it                                                                 |
| `programBrief.description`      | string, 10–4000 chars              | Minimum substance (error below 40 chars)                                                                            |
| `programBrief.sampleMessages`   | 1–5 strings, each up to 1600 chars | Restricted content, blocklist phrases, URL shorteners, CTA clarity, opt-out wording, brand identifier, sample count |
| `programBrief.messageFrequency` | string, 1–200 chars                | Blank or whitespace-only cadence (warn)                                                                             |
| `programBrief.optInDescription` | string, 10–4000 chars              | Substance (error below 20 chars), then the CTIA Call-to-Action disclosures: frequency, rates, STOP, HELP            |
| `programBrief.supportContact`   | string, 1–320 chars                | Carried through for the carriers; no rule scores it                                                                 |
| `programBrief.privacyPolicyUrl` | optional URL                       | Presence (warn when absent)                                                                                         |
| `programBrief.termsUrl`         | optional URL                       | Presence (warn when absent)                                                                                         |

### Response shape

```json theme={null}
{
  "data": {
    "score": 82,
    "verdict": "warn",
    "findings": [
      {
        "ruleId": "R-SC-CTA-HELP",
        "severity": "info",
        "field": "programBrief.optInDescription",
        "message": "…",
        "match": "…",
        "suggestion": "…"
      }
    ],
    "engine": "shortcode-preflight/v1"
  }
}
```

* `score` — 0–100; 100 means no known pattern matched.
* `verdict` — `pass`, `warn`, or `block`.
* `findings[]` — one entry per rule hit. `field` is the dot-path of the
  offending field (e.g. `programBrief.sampleMessages[2]`); `match`
  quotes the exact fragment when one exists; `suggestion` is the
  rewrite that resolves the finding.
* `engine` — the rule-engine version, stamped on every response so you
  can correlate score changes when the catalog evolves.

***

## Failure catalogue

Each finding carries a stable `ruleId`. The table covers every code the
v1 engine emits, with the remediation for each.

| `ruleId`                 | Severity | Trigger                                                                                                                 | Remediation                                                                                                                          |
| ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `R-SC-SAMPLES-COUNT`     | warn     | Fewer than 2 sample messages                                                                                            | Provide 2–5 distinct samples covering the program's real scenarios (welcome, reminder, confirmation)                                 |
| `R-SC-SAMPLE-SHAFT`      | error    | SHAFT-C promotional content (sex, hate, alcohol, firearms, tobacco, cannabis) in a sample                               | Remove the prohibited content. SHAFT verticals require a dedicated, age-gated program with explicit carrier approval                 |
| `R-SC-SAMPLE-BLOCKLIST`  | error    | A carrier content-filter phrase in a sample                                                                             | Remove or reword the phrase. Regulated verticals (e.g. licensed debt relief) need explicit carrier approval                          |
| `R-SC-SAMPLE-SHORTENER`  | error    | A public URL shortener (bit.ly, t.co, tinyurl, goo.gl, ow\.ly, buff.ly, is.gd, rb.gy, shorturl.at, cutt.ly) in a sample | Replace it with a link on your own branded domain (e.g. `links.yourbrand.com/...`)                                                   |
| `R-SC-SAMPLE-OPTOUT`     | error    | No sample carries opt-out wording                                                                                       | Add `Reply STOP to opt out.` (or UNSUBSCRIBE/QUIT/END/CANCEL) to at least one sample                                                 |
| `R-SC-SAMPLE-BRAND`      | warn     | The business name appears in no sample                                                                                  | Include the program or brand name in at least one sample so recipients recognize the sender                                          |
| `R-SC-SAMPLE-CLICK-HERE` | warn     | A bare "click here" in a sample                                                                                         | Name the destination instead ("view your order", "open the form")                                                                    |
| `R-SC-DESC-LEN`          | error    | Description under 40 characters                                                                                         | Describe who opts in, what messages the program sends, and the business value — aim for 150–300 characters                           |
| `R-SC-OPTIN-LEN`         | error    | Opt-in description under 20 characters                                                                                  | Describe the exact opt-in moment, e.g. "Subscriber enters their mobile number on yourbrand.com/join and checks the SMS consent box." |
| `R-SC-CTA-FREQ`          | warn     | No message-frequency disclosure in the opt-in CTA                                                                       | Add a frequency statement, e.g. "Recurring messages, up to 5 msgs/week."                                                             |
| `R-SC-CTA-RATES`         | warn     | Missing "Message and data rates may apply" in the opt-in CTA                                                            | Add that disclosure verbatim                                                                                                         |
| `R-SC-CTA-OPTOUT`        | warn     | No opt-out wording in the opt-in CTA                                                                                    | Add "Reply STOP to cancel"                                                                                                           |
| `R-SC-CTA-HELP`          | info     | No HELP wording in the opt-in CTA                                                                                       | Add "Reply HELP for help"                                                                                                            |
| `R-SC-FREQ-MISSING`      | warn     | `messageFrequency` is blank or whitespace-only                                                                          | State the expected cadence, e.g. "Up to 5 msgs/week" or "Msg frequency varies"                                                       |
| `R-SC-PRIVACY`           | warn     | No `privacyPolicyUrl`                                                                                                   | Provide the program's public Privacy Policy URL and link it from the opt-in page                                                     |
| `R-SC-TERMS`             | warn     | No `termsUrl`                                                                                                           | Provide the program's public Terms & Conditions URL and link it from the opt-in page                                                 |

The four Call-to-Action rules (`R-SC-CTA-*`) only evaluate once the
opt-in description clears the substance gate — a flow too thin to vet
fails `R-SC-OPTIN-LEN` first, and per-disclosure findings would be
noise until the flow is actually described.

**Scoring.** Each `error` finding deducts 25 points, each `warn` 8,
each `info` 2. The verdict is `block` when any error-severity finding
is present, `warn` when the score lands below 75, otherwise `pass`.

***

## Lifecycle: preflight → resubmission → carrier-verified

The lint sits inside the application lifecycle as an advisory
checkpoint you can run at every draft stage:

1. **Lint before the application exists.** Run the ad-hoc
   `POST /numbers/short-codes/preflight` while drafting the brief —
   before an application is even opened.
2. **Lint the stored draft.** Once the application exists (`draft`
   status), fix findings with `PATCH /numbers/short-codes/:id/brief`,
   then re-run `POST /numbers/short-codes/:id/preflight`. Repeat until
   the verdict is `pass`. Both lint endpoints are idempotent reads.
3. **Submit.** With a passing brief, `POST /numbers/short-codes/:id/submit`
   locks the brief and starts per-carrier vetting.
4. **Carrier-verified.** Carriers approve the program and the lease is
   provisioned — the verdict that actually authorizes sending. The
   preflight `pass` was the deterministic first pass; provisioning is
   the authoritative one.

The [application guide](/guides/short-code-application) walks through
each of these calls end to end.

***

## Worked carrier-complaint cycle

The loop the preflight exists to shorten: a rejection comes back from
vetting, and you need to turn a corrected brief around without
re-entering the queue blind.

1. **Read the reason.** A rejected application carries the aggregator
   or carrier `rejectionReason` — fetch it with
   `GET /numbers/short-codes/:id`: "Program rejected: published CTA
   missing opt-out wording."
2. **Open a new application.** Rejection is terminal (the brief is
   locked), so open a fresh draft with the corrected `programBrief`.
   For a CTA-wording rejection like the above, that means the opt-in
   description now carries frequency, rates, STOP, and HELP
   disclosures.
3. **Re-run the preflight against your original failure class.** Lint
   the new draft with `POST /numbers/short-codes/:id/preflight` and
   confirm the finding class is gone. For the CTA example, expect no
   `R-SC-CTA-*` findings.
4. **Resubmit when clean.** `POST /numbers/short-codes/:id/submit`
   re-enters the queue with a brief that has no known rejection
   pattern left to match.

The same cycle works pre-emptively — run it before the first
submission and the deterministic rejection never reaches a carrier.

***

See also:

* [Apply for an SMS short code](/guides/short-code-application) — the
  carrier-submission walkthrough: lease type, lifecycle statuses,
  vetting timeline, and audit events.
* [Opt-Out & Suppression Lists](/compliance/opt-out-suppression) — how
  the STOP wording your brief promises is enforced on inbound replies.
