{
  "$schema": "https://ekas.io/rules/schema.json",
  "name": "Ekas 10DLC Rule Registry",
  "description": "Every A2P 10DLC obligation Ekas checks before a brand or campaign is submitted, as structured data. One entry per requirement that can independently cause a rejection.",
  "url": "https://ekas.io/rules/10dlc/",
  "license": "https://creativecommons.org/licenses/by/4.0/",
  "updated": "2026-07-25",
  "counts": {
    "total": 915,
    "approvalGating": 823,
    "postApproval": 92,
    "automated": 745,
    "userVerified": 212,
    "deterministic": 222,
    "blocking": 508,
    "distinctObjects": 531,
    "distinctAuthorities": 70,
    "byLayer": {
      "BRAND": 196,
      "CAMPAIGN": 94,
      "CONSENT_FLOW": 101,
      "MESSAGE_CONTENT": 122,
      "OPERATIONAL": 139,
      "POLICY_PAGE": 157,
      "WEBSITE": 93,
      "CROSS": 13
    },
    "bySeverity": {
      "BLOCKING": 508,
      "HIGH": 252,
      "LOW": 27,
      "MEDIUM": 128
    },
    "byDetectability": {
      "DETERMINISTIC": 222,
      "EXTERNAL_DATA": 81,
      "AI_FORM": 329,
      "HUMAN": 44,
      "UNDETECTABLE_PRE_SUBMISSION": 67,
      "CRAWL": 180,
      "VISION": 44
    },
    "byFailureClass": {
      "RETRY_FIELD": 341,
      "TERMINAL_EXTERNAL": 155,
      "TERMINAL_EVIDENCE": 62,
      "TERMINAL_POLICY": 139,
      "TERMINAL_WEBSITE": 116,
      "HARD_STOP": 87,
      "TERMINAL_ARTIFACT": 15
    },
    "byAuthority": {
      "TCR": 433,
      "AWS": 207,
      "Bandwidth": 209,
      "Twilio": 380,
      "Telnyx": 95,
      "Plivo": 21,
      "HighLevel": 13,
      "Vonage": 21,
      "Sinch": 45,
      "Klaviyo": 15,
      "Textedly": 1,
      "T-Mobile": 127,
      "AT&T": 76,
      "Infobip": 42,
      "CTIA": 256,
      "Quo": 2,
      "Alive5": 1,
      "BeeTexting": 1,
      "FCC": 86,
      "Virginia SB 1339": 1,
      "Texas SB 140": 1,
      "Bird": 32,
      "SignalWire": 4,
      "Aegis": 4,
      "Campaign Verify": 1,
      "Commio": 2,
      "TextUs": 3,
      "Aerialink": 18,
      "LeadConnector": 2,
      "optinfix": 1,
      "Azure ACS": 2,
      "Ringover": 3,
      "Zoom (ISV)": 1,
      "GoHighLevel": 1,
      "Salesmsg": 1,
      "Attentive": 1,
      "Sakari": 4,
      "SIP.US": 1,
      "E-SIGN Act": 1,
      "Ekas (product policy)": 1,
      "FTC": 18,
      "HubSpot": 1,
      "MMA": 6,
      "state law (FL)": 1,
      "all MNOs": 4,
      "state law (ME)": 1,
      "state law": 4,
      "state law (FL, OK, MD, WA, OR, CT, NY, NJ, VA)": 1,
      "state law (UT)": 1,
      "state law (FL, OK, MD, NJ, OR)": 1,
      "state law (TN)": 1,
      "state law (TX, AZ and others)": 1,
      "state law (TX)": 1,
      "state law (VA)": 1,
      "state law (GA, WA and others)": 1,
      "courts": 1,
      "Verizon": 2,
      "all service providers": 1,
      "mytcrplus": 2,
      "PossibleNOW": 1,
      "practice": 6,
      "researcher recommendation": 1,
      "CCPA": 19,
      "Postscript": 2,
      "MessageDesk": 3,
      "GDPR": 3,
      "State law": 1,
      "MessageIQ": 1,
      "Zoom": 2,
      "EZ Texting": 1
    }
  },
  "layers": [
    {
      "id": "BRAND",
      "slug": "brand",
      "count": 196
    },
    {
      "id": "CAMPAIGN",
      "slug": "campaign",
      "count": 94
    },
    {
      "id": "CROSS",
      "slug": "cross",
      "count": 13
    },
    {
      "id": "MESSAGE_CONTENT",
      "slug": "message-content",
      "count": 122
    },
    {
      "id": "CONSENT_FLOW",
      "slug": "consent-flow",
      "count": 101
    },
    {
      "id": "WEBSITE",
      "slug": "website",
      "count": 93
    },
    {
      "id": "POLICY_PAGE",
      "slug": "policy-page",
      "count": 157
    },
    {
      "id": "OPERATIONAL",
      "slug": "operational",
      "count": 139
    }
  ],
  "rules": [
    {
      "id": "BRD-001",
      "slug": "brd-001",
      "title": "A legal company name is required for every entity but a sole proprietor",
      "statement": "The legal company name field must be populated for every entity type except sole proprietor, where TCR wants it omitted and the person's name used instead.",
      "rationale": "The legal name is what every downstream check keys on — the IRS match, the duplicate-brand check, the vetting report — so an empty one does not fail politely at the end, it makes the whole registration unverifiable. The sole-proprietor carve-out is the part that catches people: submitting a company name there is as wrong as omitting one anywhere else.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Enter the legal entity name exactly as it appears on the IRS CP-575 or 147C letter. For a sole proprietor, leave this field empty and populate the first and last name fields instead. Done when the name here matches the tax record character for character.",
      "example": "company_name: Acme Coffee Co, LLC",
      "pitfalls": [
        "A sole proprietor who types their trading name here is routed into the wrong verification path and fails an identity check that was never meant to run."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-002",
      "slug": "brd-002",
      "title": "Legal name must match the IRS record for the EIN",
      "statement": "The registered legal company name must match the IRS CP-575 / 147C record for the submitted EIN, character for character.",
      "rationale": "Vetting queries IRS records directly, so the legal name is matched mechanically rather than judged. Punctuation and capitalisation differences that a human would ignore — \"ACME, Inc.\" against \"Acme Inc\" — fail the comparison, which is why this rejects registrations that look obviously correct to their owner.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name + brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30900",
          "remediable": true
        },
        {
          "provider": "AWS",
          "code": "BRAND_TAX_ID_MISMATCH",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the exact legal name printed on your IRS CP-575 confirmation letter (or request a 147C letter from the IRS). Punctuation and capitalisation matter: \"ACME, Inc.\" and \"Acme Inc\" are different records.",
      "notes": "We cannot verify this without an IRS lookup, so we surface it as a pre-submission warning with the evidence the user should check, never as a pass.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the legal name on the brand match the IRS CP-575 or 147C letter character for character, punctuation included?",
        "howToCheck": [
          "Open the CP-575 the IRS issued when the EIN was assigned. If it has been lost, call the IRS Business & Specialty Tax Line on 800-829-4933 and request a 147C — it is free and it is the same authoritative name.",
          "Read the name off the letter and compare it to the brand field one character at a time: commas, full stops, a leading \"The\", and the entity suffix all count."
        ],
        "failureLooksLike": "\"ACME, Inc.\" on the letter and \"Acme Inc\" in the form. A person reads those as the same company; the comparison is mechanical and reads them as two different records."
      }
    },
    {
      "id": "BRD-003",
      "slug": "brd-003",
      "title": "Legal name must carry its exact entity suffix as filed",
      "statement": "The registered legal name must include the entity suffix exactly as filed — no abbreviation, expansion, or omission.",
      "rationale": "Vetting matches your legal name against the IRS record as a string, so \"Acme Coffee Company\" does not match \"Acme Coffee Co\" and neither matches \"Acme Coffee\". Businesses reliably enter the name they use day to day rather than the one on their formation documents, and the resulting mismatch reads as a failed identity check rather than a typo.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Copy the legal name character for character from your IRS CP-575 letter or formation documents, including the suffix and its punctuation. Put the everyday name in the brand display name field instead.",
      "example": "Legal name: \"Acme Coffee Co, LLC\" · Display name: \"Acme Coffee\"",
      "pitfalls": [
        "Dropping the comma before LLC, or writing \"Incorporated\" where the filing says \"Inc.\", are both real mismatches."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-006",
      "slug": "brd-006",
      "title": "Submit every name line above the address on a multi-line IRS record",
      "statement": "Where the IRS CP-575 or 147C prints the entity name across more than one line, every line above the address line belongs in the legal name, and nothing below it.",
      "rationale": "The IRS wraps long entity names onto a second line, and a business reading its own letter sees the first line as \"the name\" and the rest as an artefact of the page. Vetting compares the full registered string, so dropping the continuation is a name mismatch — and it is the hardest kind to spot, because the value in the field is visibly the company.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open the CP-575 or 147C and read down to the address block. Concatenate every name line above it, in order, separated by a single space, into the legal name field. Done when the field holds the whole name as printed and stops at the line before the street address.",
      "example": "company_name: Acme Coffee Co, LLC — including the \"& Roasting Company\" continuation line, if the letter carries one",
      "pitfalls": [
        "The line below the name is the \"C/O\" or care-of line on many letters, and it looks like part of the name. It is not — including it fails the match as surely as omitting the continuation does."
      ],
      "notes": "In DIRECT CONFLICT with BRD-007, which is TCR's CSP User Guide instruction to submit only the first line. The catalog keeps both deliberately: the correct answer depends on the submission target, and neither is safe to apply universally. Where the destination is unknown, submit the full name and be ready to shorten it — Twilio, the stricter of the two, is also the more common target.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-007",
      "slug": "brd-007",
      "title": "TCR takes only the first line of the IRS-registered name",
      "statement": "On a submission that goes to TCR directly, the company name is the first line of the IRS-registered name and nothing after it.",
      "rationale": "TCR's CSP User Guide asks for the first line where Twilio asks for all of them, so the same IRS letter yields two different correct answers depending on where the registration is filed. A team that standardised on one form and then changed provider gets a name mismatch on a value that was right last month, which is a genuinely baffling rejection.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "For a direct TCR submission, take the first line of the IRS-registered name only. Done when the field matches line one of the letter exactly, with no continuation appended.",
      "example": "company_name: Acme Coffee Co, LLC — the first line, where the letter continues \"& Roasting Company\" on line two",
      "notes": "Conditional on the submission target, and NOT tagged with `providers` because TCR is the registry every route ends at rather than a provider we can select. The condition lives in the criteria instead, which open with a PASS boundary. In direct conflict with BRD-006 (Twilio); the catalog says explicitly not to merge them, because the two predicates disagree on the same input.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-008",
      "slug": "brd-008",
      "title": "Legal name must not carry an appended personal name or role",
      "statement": "The legal name field must contain only the entity name — not a contact person or role suffix appended to it.",
      "rationale": "Registration forms elsewhere often combine entity and contact, so people paste strings like \"ACME LLC Theresa Lopez - MBR\" into the legal name. That never matches the IRS record, and because the entity name is present inside the string it looks correct at a glance.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove everything after the entity suffix. The contact person belongs in the brand contact fields, not in the legal name.",
      "example": "Legal name: \"Acme Coffee Co, LLC\" — not \"Acme Coffee Co, LLC Jane Doe - MBR\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-009",
      "slug": "brd-009",
      "title": "The legal name field takes the legal name, not the trading name",
      "statement": "A DBA, trade name, subsidiary name, or product name must not be submitted in the legal company name field.",
      "rationale": "Almost every business has two names and only one of them is on the tax record. The trading name is the one on the sign, the invoices, the website and in everyone's head, so it is the one that gets typed — and it fails the IRS match, which returns as \"we could not verify this business\" rather than as \"wrong name\". Product names are the same trap one level down: the messaging programme is for a product, so the product name feels like the subject of the registration.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name vs brand.dba",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS",
        "Plivo",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30918",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30914",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move the trading name into the DBA field and put the name from the IRS letter in the legal name field. Both fields are submitted, so nothing is lost — the trading name is what reviewers compare against the website, and the legal name is what the tax match runs on. Done when the two fields hold different values and each is in the right box.",
      "example": "company_name: Acme Coffee Co, LLC · dba: Acme Coffee — not \"Acme Coffee\" in both",
      "pitfalls": [
        "A subsidiary registering under the parent's name fails the same way in reverse: the EIN belongs to the subsidiary, so the parent name matches nothing."
      ],
      "notes": "Confirming which name is on the tax record needs the IRS file, so the judged half only catches the recognisable shapes — a suffix-less legal name, or a product name in the entity field. The user has to confirm against their CP-575 that the value they submitted is the issued name.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is the value in the legal name field the name printed on the IRS letter, rather than the trading name, a subsidiary name, or the product the messages are about?",
        "howToCheck": [
          "Put the two names side by side: the one on the CP-575, and the one on the sign, the invoices and the website.",
          "The IRS name goes in the legal name field; the trading name goes in the DBA field. Both are submitted, so nothing is lost."
        ],
        "failureLooksLike": "\"Acme Coffee\" in the legal name field and nothing in the DBA field. The tax match returns \"we could not verify this business\" rather than \"wrong name\", so the EIN gets checked repeatedly instead."
      }
    },
    {
      "id": "BRD-010",
      "slug": "brd-010",
      "title": "Take the legal name from the CP-575 or 147C, never a W-2 or W-9",
      "statement": "The legal name must come from the IRS CP-575 confirmation letter or a 147C letter — the name on a W-2 or W-9 is not authoritative for this purpose.",
      "rationale": "A W-9 is the document a business has to hand and fills in itself, so the name on it is whatever someone typed; the CP-575 is what the IRS actually issued. They differ more often than people expect — abbreviations, a dropped \"The\", a payroll-system truncation — and vetting matches against the issued record. The business is not being careless here; it is using the tax document it was asked for last time.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio",
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Find the CP-575 issued when the EIN was assigned and copy the name from it. If it has been lost, call the IRS Business & Specialty Tax Line and request a 147C letter — it is free, and it is the same authoritative name. Done when the field matches the letter, not the W-9.",
      "example": "company_name: Acme Coffee Co, LLC — as printed on the CP-575, not as typed on last year's W-9",
      "notes": "Off-platform: we cannot see which document the name came from. Surfaced as the question the user has to answer themselves — \"which piece of paper is this name on?\" — before submitting, because it is far cheaper to check now than after a failed vet. A 147C request takes one phone call and the IRS will read the name back.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Which piece of paper did this legal name come from — a CP-575 or 147C, or a W-2 or W-9?",
        "howToCheck": [
          "Ask whoever supplied the name which document they read it off.",
          "If the answer is a W-9, a W-2, an invoice or a bank record, go back to the CP-575 — the W-9 is a form the business filled in itself, so the name on it is whatever someone typed.",
          "No CP-575 on file: request a 147C from the IRS, which reads the issued name back to you."
        ],
        "failureLooksLike": "The name on the W-9 has been shortened by a payroll system or lost a leading \"The\", the business has used it for years, and vetting matches against the issued record instead."
      }
    },
    {
      "id": "BRD-011",
      "slug": "brd-011",
      "title": "A legal business name may back only one brand across the whole registry",
      "statement": "A legal business name already registered as a brand at TCR cannot be registered again, by anyone.",
      "rationale": "Brand uniqueness is registry-wide, not per account: if another CSP has already registered this business, the second submission collides with a record neither you nor the customer can see. Agencies hit this constantly when a client registered themselves first, or moved between platforms and left the old brand behind, and the rejection names a duplicate the customer will swear does not exist.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30703",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Ask the customer whether they have ever registered 10DLC before, with anyone — including a previous agency or a platform they no longer use. If they have, the existing brand must be released or shared rather than duplicated; contact the CSP that holds it. Done when either no prior brand exists or the existing one has been transferred to you.",
      "notes": "Off-platform: the duplicate lives in TCR, which we cannot query. The user has to establish for themselves whether the business has an existing brand — the reliable way is to ask the customer directly who has previously sent SMS on their behalf, since a forgotten registration from a former vendor is the usual cause. Do not resubmit hoping for a different answer; the check is exact and repeatable.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has this business ever registered a 10DLC brand before, with anyone — including a former agency or a platform it no longer uses?",
        "howToCheck": [
          "Ask the customer directly who has sent SMS on their behalf in the past, not whether they have a brand. A forgotten registration from a previous vendor is the usual cause.",
          "Where a prior brand exists, contact the CSP holding it to release or share it rather than registering a second one."
        ],
        "failureLooksLike": "The submission is refused as a duplicate of a brand the customer will swear does not exist, because it was created by an agency they parted with two years ago and it is invisible to both of you."
      }
    },
    {
      "id": "BRD-012",
      "slug": "brd-012",
      "title": "Name fields must read as natural business text",
      "statement": "The display name, company name, and any first and last name fields must contain real, natural text — not obfuscated, garbled, or placeholder strings.",
      "rationale": "Obfuscated names are how bad actors evade brand-level blocklists, so the check exists to catch deliberate garbling — but it also catches honest submissions, because an internal reference like \"ACME-STORE-04\" or a keyboard-mashed test value left in place looks identical to evasion from the outside. The reviewer cannot tell the difference and does not try.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.display_name + brand.company_name + brand.first_name + brand.last_name",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30756",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "556",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the value with the name a customer would recognise, written the way it appears on the shopfront or the website. Internal identifiers, store codes and account numbers belong in the reference id field. Done when every name field reads as something you could say aloud to a customer.",
      "example": "display_name: Acme Coffee — not \"ACME-STORE-04\", \"acme test\", or \"Xx_acme_xX\"",
      "pitfalls": [
        "A test brand created during an integration and then promoted to production carries the test name with it, and nothing in the promotion path re-reads that field."
      ],
      "notes": "TCR enforces the obfuscation check explicitly on sole-proprietor brands, where the first and last name fields are the identity; Twilio 30756 applies it to every brand. Authored universally, per the strict-superset model.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-013",
      "slug": "brd-013",
      "title": "A brand must be a business, not a person",
      "statement": "Outside the sole-proprietor tier, the registered brand must be a business entity rather than an individual's personal identity.",
      "rationale": "A2P messaging is registered to businesses because the whole accountability chain — the tax record, the vetting report, the complaint route — hangs off a business existing. An individual registering under their own name outside the sole-proprietor tier has no such record, so nothing can be verified and the brand is refused. It happens honestly: consultants, coaches and creators genuinely trade as themselves, and the standard tier is the one the form defaults to.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name + brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30928",
          "remediable": true
        }
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "If the business is a registered entity, use its registered name and EIN. If it genuinely is you trading as yourself, switch to the sole-proprietor tier, which verifies by mobile OTP instead of by tax record and expects exactly a personal name. Done when the entity type and the name agree about what is being registered.",
      "example": "company_name: Acme Coffee Co, LLC on the standard tier — or \"Jane Doe\" on the sole-proprietor tier, never a person's name on the standard one",
      "pitfalls": [
        "Adding \"Consulting\" to your own name does not create an entity. If there is no registration behind the words, the standard tier still has nothing to verify."
      ],
      "notes": "The inverse of BRD-068, which requires a personal name on the sole-proprietor tier. The two are tagged to opposite sides of the same dimension so exactly one fires per registration.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-014",
      "slug": "brd-014",
      "title": "An acquired business registers the details it filed taxes under",
      "statement": "Where the business has been acquired, the brand must carry the company information used for tax reporting at the time — not the acquirer's.",
      "rationale": "After an acquisition the operating business keeps trading under its own name while its paperwork moves to the parent, and whoever fills in the registration reaches for the current org chart. Vetting looks up the EIN that actually filed, so the parent's name against the subsidiary's EIN — or the reverse — verifies as a mismatch. It is one of the few brand failures where both names are genuinely the company.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name + brand.ein",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Establish which entity filed the most recent federal return for this business and register that name and that EIN together. If the acquisition consolidated the tax filing under the parent, register the parent; if the subsidiary still files separately, register the subsidiary. Done when the name and the EIN come from the same return.",
      "example": "company_name: Acme Coffee Co, LLC with EIN 12-3456789 — the entity that filed, even where the parent now owns it",
      "notes": "Off-platform: nothing on the form says an acquisition happened. The user has to answer this themselves by asking their finance team which entity filed the last federal return, and then take BOTH the name and the EIN from that return rather than mixing one from each side of the deal.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Which entity filed the most recent federal tax return for this business — and are the name and the EIN on this brand both taken from that return?",
        "howToCheck": [
          "Ask the finance team, not the org chart: after an acquisition the operating business keeps trading under its own name while the filing may have moved to the parent.",
          "Take the legal name and the EIN from the same return. Never one from each side of the deal."
        ],
        "failureLooksLike": "The parent's name against the subsidiary's EIN. Both are genuinely the company, and the lookup returns a mismatch that reads like a typo."
      }
    },
    {
      "id": "BRD-015",
      "slug": "brd-015",
      "title": "A brand covers exactly one sender",
      "statement": "Each brand must be scoped to a single message sender — one brand per company, client, franchise or subsidiary, and no number shared between brands.",
      "rationale": "The brand is what a consumer would use to work out who texted them, so a brand covering several companies makes that question unanswerable and puts each company's complaint history on the others' record. Franchise groups and agencies reach it by economy: one registration is cheaper than forty, and the Agents & Franchises use case sounds like permission to do exactly that. It is not — it describes messaging on behalf of franchisees, not registering them as one brand.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand record",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30926",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "710",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register one brand per legal entity that sends, each with its own EIN, website and contact details, and give each brand its own numbers. Where you manage many of them, that relationship belongs on the CSP record rather than being folded into a single brand. Done when every company named anywhere in the campaign has its own brand.",
      "example": "One brand for Acme Coffee Co, LLC and a separate brand for each franchisee — not one \"Acme Coffee Group\" brand covering all of them",
      "pitfalls": [
        "Sharing one phone number across brands fails for the same reason from the other direction: the number is what the carrier attributes traffic by, so two brands on one number cannot be told apart."
      ],
      "notes": "Absorbs BRD-162 (one brand per end customer, stated as the ISV-side obligation) and OPS-367 (no number shared across brands). Distinct from BRD-160, which asks whether the RIGHT entity was registered; this rule asks whether ONE entity was.",
      "catalogIds": [
        "BRD-162",
        "OPS-367"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-017",
      "slug": "brd-017",
      "title": "A tax ID is required for every entity but a sole proprietor",
      "statement": "A tax ID or business registration number must be supplied for every entity type except sole proprietor.",
      "rationale": "The tax ID is the identifier the brand is actually verified against, so without it there is nothing to check the name and address into — the registration cannot be approved, only held. Sole proprietors are the deliberate exception because they are verified by mobile OTP instead, which is the whole point of that tier.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Enter the nine-digit EIN from your CP-575 or 147C letter. Non-US entities supply the registration number their home country issues. Done when the identifier is present and matches the document it came from.",
      "example": "ein: 12-3456789",
      "pitfalls": [
        "A brand-new EIN can be present and still fail verification because the IRS has not published it yet — that is BRD-027, and the fix is to wait rather than to change the number."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-019",
      "slug": "brd-019",
      "title": "EIN must be nine digits",
      "statement": "The EIN must be exactly nine digits (formatting characters aside).",
      "rationale": "The EIN is the key the whole brand verification hangs on: it is what links your registration to a real, IRS-recognised entity. A malformed one cannot be looked up at all, so the registration fails before any judgement about your business is made.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Enter the nine-digit EIN from your IRS documentation, e.g. 12-3456789.",
      "example": "EIN: 12-3456789",
      "catalogIds": [
        "BRD-018"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-020",
      "slug": "brd-020",
      "title": "Submit the US EIN hyphenated",
      "statement": "A US EIN must be submitted in the hyphenated XX-XXXXXXX form.",
      "rationale": "Several providers match the EIN as a string rather than as a number, so an unhyphenated one misses a record that is sitting right there — and the rejection says the business could not be verified, which sends people off to check their address and their name. The hyphen is free to add and every provider that does not require it accepts it anyway.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Telnyx",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reformat the EIN as two digits, a hyphen, then seven digits. Done when the value reads 12-3456789 rather than 123456789.",
      "example": "ein: 12-3456789",
      "notes": "Three providers require the hyphen and the rest accept it, so this is authored universally rather than provider-tagged: the strict-superset form costs nothing and a tag here would silently drop the requirement for the providers that do enforce it. Distinct from BRD-019, which checks there are nine digits at all — the catalog says explicitly not to merge them.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-021",
      "slug": "brd-021",
      "title": "An SSN is not a business registration number",
      "statement": "A Social Security Number must not be submitted in the business registration number field.",
      "rationale": "A sole proprietor with no EIN reaches for the number they do have, and the field accepts nine digits without complaint. It fails verification — an SSN is not in any business register — and in the meantime a government identifier has been sent to a registry that had no business receiving it. The correct route for that user is the sole-proprietor tier, which asks for no tax ID at all.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the SSN. If the business has an EIN, use it; if it does not, register on the sole-proprietor tier, which verifies by mobile OTP instead. Done when the field holds an EIN or is empty on a sole-proprietor brand.",
      "example": "ein: 12-3456789 (an EIN) — never 123-45-6789 (an SSN)",
      "pitfalls": [
        "Applying for an EIN is free and takes minutes on the IRS site, so the sole-proprietor tier is a choice rather than the only option — but it is the right one below the volume threshold."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-022",
      "slug": "brd-022",
      "title": "A DUNS number is not a US business registration number",
      "statement": "A DUNS number must not be submitted as the US business registration number.",
      "rationale": "DUNS is a commercial identifier issued by a credit bureau, and the registry checks against tax records — so a DUNS number is nine digits that match nothing. It reaches the field honestly, because procurement and vendor-onboarding systems ask for DUNS constantly and it feels like the same class of thing.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Replace it with the EIN from your IRS letter. Keep the DUNS number for the systems that want it — it is simply not what this field verifies against. Done when the value is the nine-digit EIN in hyphenated form.",
      "example": "ein: 12-3456789 — the IRS EIN, not the nine-digit D-U-N-S",
      "notes": "Only the shape is checkable here: an unhyphenated nine-digit US identifier is the DUNS signature, since a real EIN should carry the hyphen (BRD-020). Confirming a value IS a DUNS number needs the D&B register, which is EXTERNAL_DATA and out of reach pre-submission.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-023",
      "slug": "brd-023",
      "title": "The identifier type must be one of the supported values",
      "statement": "Where a business-registration identifier type is declared, it must be one of EIN, DUNS, CBN, CN, ACN, CIN, VAT, VATRN, RN or Other.",
      "rationale": "The type tells the registry which register to look the number up in, so an unrecognised value does not degrade to a best guess — the lookup never runs and the brand stays unverified. Integrations produce this by passing through whatever their own CRM calls the field, and the value looks perfectly reasonable right up to the point where nothing happens.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_registration_identifier",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30799",
          "remediable": true
        }
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Set the identifier type to the code for the document the number came from: EIN for a US employer identification number, CBN for a Canadian business number, VAT or VATRN for a European VAT registration, and Other only when none of them fits. Done when the value is one of the ten codes, in upper case.",
      "example": "business_registration_identifier: EIN · ein: 12-3456789",
      "pitfalls": [
        "\"TAX_ID\", \"FEIN\" and \"Federal Tax ID\" all mean EIN and are all rejected — the enum takes the short code, not the name people use."
      ],
      "notes": "A US EIN passes without an explicit type because the value is unambiguous and TCR infers it, so the check only reports a value that is present and outside the enum. Declaring it anyway is worth doing: the Twilio Trust Hub API takes the field directly and rejects the submission when it is missing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-024",
      "slug": "brd-024",
      "title": "The identifier value must match the type declared for it",
      "statement": "The business-registration identifier value must be in the form its declared type requires.",
      "rationale": "Declaring EIN and supplying a Canadian business number — or the reverse — sends a well-formed number to the wrong register, and the answer comes back as \"business not found\" rather than as a type error. It is a common integration defect precisely because both values are nine digits and both fields validate.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_registration_identifier + brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the two fields against each other: the type names the document, the value is the number printed on it. If they disagree, correct whichever one is wrong rather than changing both. Done when the number is the one the named register issued.",
      "example": "business_registration_identifier: EIN · ein: 12-3456789 — nine digits, because that is what an EIN is",
      "notes": "Distinct from BRD-019, which asks only whether a US EIN has nine digits. This rule asks whether the value fits the type that was DECLARED for it, which is a different failure and produces a different fix. A type outside the enum is BRD-023 and is left to it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-025",
      "slug": "brd-025",
      "title": "The issuing country must be declared, and must match the country of registration",
      "statement": "einIssuingCountry must be a valid ISO-3166 alpha-2 code and must equal the country the business is registered in.",
      "rationale": "TCR defaults this field to US when it is left unset, so a foreign brand that omits it is silently looked up in the US registers and fails as an unverifiable business rather than as a missing field. The default is the trap: the registration looks complete, and the one field that would have routed it correctly is the one nobody filled in.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein_issuing_country + brand.country",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Klaviyo"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set einIssuingCountry to the two-letter code of the country that issued the identifier, and make sure the brand country matches it. Done when both fields hold the same ISO-3166 alpha-2 code, in upper case.",
      "example": "ein_issuing_country: US · country: US — or GB and GB for a UK company number",
      "pitfalls": [
        "A US subsidiary of a foreign parent registers with US and its own EIN. The issuing country follows the identifier, not the head office."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-026",
      "slug": "brd-026",
      "title": "An alternate identifier needs its type alongside it",
      "statement": "Where altBusinessId is supplied, altBusinessIdType must be one of DUNS, GIIN, LEI or NONE.",
      "rationale": "The alternate identifier is optional and strengthens verification when it is present, but a number with no type is a number nobody can look up — the registry rejects the pair rather than storing half of it. It usually happens when a DUNS number is added helpfully, from a procurement record that never carried a type field.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.alt_business_id + brand.alt_business_id_type",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set altBusinessIdType to the register the alternate number came from — DUNS for Dun & Bradstreet, GIIN for the IRS FATCA register, LEI for a legal entity identifier. If you do not have one, clear both fields rather than sending a bare number. Done when either both fields are populated or neither is.",
      "example": "alt_business_id: 123456789 · alt_business_id_type: DUNS",
      "notes": "The field is optional and validated only when present, which is why this is LOW rather than BLOCKING — but it will reject the whole brand submission, so it costs a round trip like any other field error.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-027",
      "slug": "brd-027",
      "title": "EIN must be old enough to have propagated to IRS records",
      "statement": "A newly issued EIN is not yet present in the records vetting providers query; registration fails until it propagates.",
      "rationale": "A newly issued EIN is not yet visible to the databases vetting providers query, so verification fails against a record that does not exist yet. No form correction fixes this — the only remedy is time, which is worth knowing before paying a non-refundable vetting fee.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS",
        "Twilio",
        "Bandwidth"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "If your EIN was issued in the last 90 days, wait before submitting. No form correction fixes this — vetting queries a record that does not exist yet.",
      "notes": "Providers publish different windows (AWS 30 days, Twilio 30-90, Bandwidth 90). Strictest kept per the strict-superset model.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Was this EIN issued more than 90 days ago?",
        "howToCheck": [
          "Find the issue date printed on the CP-575 confirmation letter.",
          "Count the days to today. Under 90, wait rather than submit — providers publish windows from 30 to 90 days and the longest is the one worth planning against."
        ],
        "failureLooksLike": "An EIN issued three weeks ago matches nothing, and the rejection says the business could not be found rather than that the EIN is too new — so a perfectly correct name and address get edited over and over."
      }
    },
    {
      "id": "BRD-028",
      "slug": "brd-028",
      "title": "A business absent from the verification databases must upload its registration document",
      "statement": "Where the entity does not appear in third-party verification databases, an official registration or IRS document must be uploaded with the brand.",
      "rationale": "Commercial business databases lag reality by months, so a genuinely new company is missing from them for no fault of its own and the automated check returns \"entity not found\". The document upload is the intended route around that, not an escalation — but nothing in the form suggests it exists, so new businesses conclude they are ineligible and give up.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "registration document attachment",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach the CP-575 or 147C letter, or the state filing certificate, as a PDF with the registration. Scan the whole page rather than cropping to the name. Done when the document showing the legal name, the identifier and the address is attached to the submission.",
      "notes": "Whether the entity is present in the commercial databases is something only the provider can see. What the user has to do is have the document ready before submitting rather than after the rejection — the CP-575 for a US entity, or the incorporation certificate from the national register elsewhere — because the resubmission window is where most of the delay accumulates.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you have the registration document scanned and ready to attach before you submit, rather than after a rejection?",
        "howToCheck": [
          "For a US entity: the CP-575 or a 147C letter, or the state filing certificate.",
          "Elsewhere: the incorporation certificate from the national register.",
          "Scan the whole page, not a crop of the name — the document has to show the legal name, the identifier and the address together."
        ],
        "failureLooksLike": "A genuinely new company is missing from the commercial databases, reads the \"entity not found\" result as ineligibility, and abandons the registration — when the document upload was the intended route through."
      }
    },
    {
      "id": "BRD-032",
      "slug": "brd-032",
      "title": "Submit the identifier in the form the issuing country requires",
      "statement": "For a country on TCR's VAT-optimised list, submit only the numeric portion of the VAT ID; elsewhere submit the corporation registration number or national tax ID.",
      "rationale": "A European VAT number is normally written with its country prefix — GB123456789 — and that prefix is exactly what TCR's lookup does not want. The number is right, the format is wrong, and the rejection says the business could not be verified, which sends people to check their company name. For countries off the optimised list the whole identifier is different: the VAT number will not verify at all and the corporation registration number is what is wanted.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Telnyx",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip the two-letter country prefix from a VAT number and submit the digits only. If your country is not on TCR's VAT-optimised list, submit the corporation registration number from your national companies register instead of the VAT number. Done when the identifier is the bare number the register itself would search on.",
      "example": "ein: 123456789 for a GB VAT registration written GB123456789 — the digits only",
      "pitfalls": [
        "Which countries are on the optimised list changes, and TCR publishes it rather than deriving it. Check the current list before assuming your country qualifies."
      ],
      "notes": "Merges the two branches of one decision (the catalog's BRD-032 and BRD-033): the numeric VAT portion on the optimised list, the corporation registration number off it. Only the first branch is mechanical — the check reports an identifier still carrying its country prefix. Choosing between VAT and corporation number needs the current TCR country list, which we do not hold, so the user has to confirm their country against it themselves before deciding which number to send.",
      "catalogIds": [
        "BRD-033"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-034",
      "slug": "brd-034",
      "title": "Validate a non-US identifier against its home register before submitting",
      "statement": "A non-US registration identifier should be confirmed against the issuing country's official register before it is submitted.",
      "rationale": "A foreign brand only gets a few automated attempts before it lands in the appeal queue, and an identifier that is stale, superseded or transcribed wrongly burns one of them for no reason. The home register is free to search and authoritative, which makes this the cheapest five minutes in the whole non-US registration path.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Look the number up before submitting: VIES for an EU VAT number, ABN Lookup for Australia, NZBN for New Zealand, the Companies Registry for Hong Kong, Companies House for the UK. Done when the register returns your company, spelled the way you are about to submit it.",
      "notes": "We cannot reach these registers. The user runs the lookup themselves and, crucially, copies the legal name back from the register rather than from their letterhead — the name the register holds is the one the vetting provider will compare against.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Did you look this identifier up in the issuing country's official register, and copy the legal name back from what the register returned?",
        "howToCheck": [
          "Search the authoritative register: VIES for an EU VAT number, ABN Lookup for Australia, NZBN for New Zealand, the Companies Registry for Hong Kong, Companies House for the UK.",
          "Copy the company name from the register's result, not from your letterhead. That is the name the vetting provider compares against."
        ],
        "failureLooksLike": "A number that is stale, superseded, or transcribed one digit wrong burns one of the few automated attempts a non-US brand gets before it lands in the appeal queue."
      }
    },
    {
      "id": "BRD-035",
      "slug": "brd-035",
      "title": "The country of registration must be on the permitted list",
      "statement": "A brand registered in a country outside TCR's permitted-country list cannot be registered at all.",
      "rationale": "This is an eligibility gate rather than a data problem: some countries are excluded from the US A2P framework entirely, usually for sanctions or fraud-rate reasons, and no amount of correct paperwork moves a brand from the wrong side of it. Finding out late is expensive because everything else in the onboarding will have succeeded first.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.country + brand.ein_issuing_country",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "529",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the country against TCR's current permitted list before collecting anything else from the customer. Where the country is excluded, the only route is a US or otherwise permitted entity that genuinely owns the customer relationship — not a nominee registration under someone else's name.",
      "notes": "TCR publishes and revises the list; we do not hold it. The user has to check their country against the current published list before starting, and must not work around an exclusion by registering under a related entity in a permitted country — that is a fabricated registration (BRD-286) and carries a much worse outcome than a refusal.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the country of registration on TCR's current permitted-country list?",
        "howToCheck": [
          "Check the country against the list TCR publishes and revises, before you collect anything else from the customer.",
          "Where the country is excluded, the only legitimate route is a US or otherwise permitted entity that genuinely owns the customer relationship."
        ],
        "failureLooksLike": "Everything else in the onboarding succeeds first and the eligibility gate is discovered last. Registering under a related entity in a permitted country to get around it is a fabricated registration, which is a far worse outcome than a refusal."
      }
    },
    {
      "id": "BRD-036",
      "slug": "brd-036",
      "title": "A non-US brand should expect automated identity verification to fail",
      "statement": "A brand registered outside the US will usually fail the initial automated identity check and must budget for an appeal or an external vet.",
      "rationale": "The automated check queries US business registers, so a foreign company is absent from them for the ordinary reason that it is foreign. The result is an UNVERIFIED brand and a rejection that reads like a data error, and teams spend days re-checking a perfectly correct name and address before discovering the path was never going to work. Knowing it in advance turns a mystery into a scheduled step.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.country",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Plan for the identity-status appeal before submitting: have the home-country registration certificate ready as a PDF, and expect to order an external vet if the appeal does not clear it. Do not change correct brand data in response to the first UNVERIFIED result.",
      "notes": "Not a defect in the registration and not fixable by editing it — this rule exists to set the expectation. What the user has to do themselves is gather the evidence an appeal will need (the official registration extract from their national register, in English or with a certified translation) and allow for the extra week the appeal takes.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Do you have the home-country registration extract ready as a PDF, in English or with a certified translation, before you submit?",
        "howToCheck": [
          "Download the official extract from your national register — Companies House, the Handelsregister, the ASIC extract, whichever applies.",
          "Confirm the legal name on it is spelled exactly as you are about to submit it.",
          "Budget an extra week for the identity appeal, and do not edit correct brand data in response to the first UNVERIFIED result."
        ],
        "failureLooksLike": "A correct non-US brand comes back UNVERIFIED because the automated check queries US registers only, and the team spends days re-checking a name and address that were right all along."
      }
    },
    {
      "id": "BRD-037",
      "slug": "brd-037",
      "title": "Low-Volume Standard is not available to a non-US brand that needs vetting",
      "statement": "A non-US brand cannot stay on the Low-Volume Standard class once additional vetting is required of it.",
      "rationale": "Low-Volume Standard is the cheap class that skips external vetting, and a foreign brand is exactly the case where vetting cannot be skipped. Choosing it is rational — the volumes are genuinely low — and it produces a registration that can never complete, because the class forbids the step the brand needs.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.country + brand class",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register the non-US brand as Standard and order the external vet it will need. The throughput starts lower than a vetted US brand and rises once the vet completes. Done when the brand class is STANDARD rather than LOW_VOLUME_STANDARD.",
      "example": "brand_class: STANDARD · country: GB — rather than LOW_VOLUME_STANDARD",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-038",
      "slug": "brd-038",
      "title": "Some providers require an external vet for every non-US brand",
      "statement": "At least one provider requires external vetting on every non-US customer before the brand can be used.",
      "rationale": "Where this applies it is a precondition rather than a remedy: the brand does not proceed at all until the vet is bought, so discovering it after the customer's data is collected wastes the submission and the fee for the class of vet that was ordered first. It is easy to miss because the requirement sits in the provider's onboarding terms rather than in any field validation.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.country + external vet record",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Vonage"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Order the external vet before submitting the brand, and budget its fee into the customer's onboarding. Done when a completed vet is attached to a non-US brand on a provider that requires one.",
      "notes": "Conditional on the submission target — Vonage states it explicitly for every non-US customer — and NOT tagged with `providers`, because the tag budget is spent on genuine baseline divergences and this one is checkable from the target we already hold. The condition lives in the check, which passes immediately for a US brand and reports UNCLEAR when the target is unknown. What the user must confirm themselves is whether their own provider imposes the same precondition: it is in the onboarding terms, not in any API error.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you checked your provider's onboarding terms for a mandatory external vet on non-US customers, and budgeted its fee?",
        "howToCheck": [
          "Read the provider's onboarding or acceptable-use terms rather than the API reference — the requirement lives there and produces no field validation error.",
          "Where it applies, order the vet before submitting the brand, and price it into the customer's onboarding."
        ],
        "failureLooksLike": "The brand is submitted, the fee for the wrong class of vet is spent first, and the requirement surfaces only as a support answer after the customer's data is already collected."
      }
    },
    {
      "id": "BRD-039",
      "slug": "brd-039",
      "title": "The sending-volume tier decides whether the sole-proprietor path is available",
      "statement": "A registration that requests a volume tier requiring a tax ID cannot be filed on the sole-proprietor path.",
      "rationale": "The sole-proprietor tier trades verification for throughput: it exists because an individual has no EIN, and it caps sending hard as a consequence. Choosing the tier and then requesting a volume it does not support produces a registration that is internally contradictory, and the platform resolves the contradiction by refusing the tier rather than by lowering the volume. Customers reach it by picking their plan first and their entity type second.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type + requested volume tier",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Textedly",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30903",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Either drop to the volume the sole-proprietor tier supports — roughly 1,000 messages a day — or obtain an EIN and register as a standard brand at the volume you actually need. Done when the requested class and the entity type describe the same tier.",
      "example": "entity_type: SOLE_PROPRIETOR with no brand class above it — or entity_type: PRIVATE_PROFIT with brand_class: STANDARD",
      "pitfalls": [
        "The cap is enforced by the CSP rather than by the carrier, so the traffic is not rejected message by message — it is throttled or the account is reviewed, which is harder to diagnose than a rejection."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-040",
      "slug": "brd-040",
      "title": "Entity type must be one of the five supported values",
      "statement": "entityType must be exactly one of PRIVATE_PROFIT, PUBLIC_PROFIT, NON_PROFIT, GOVERNMENT or SOLE_PROPRIETOR.",
      "rationale": "Entity type decides which verification path runs, which use cases are available, and which other fields are mandatory, so a value outside the enum does not degrade gracefully — it rejects at the API boundary before any of that is evaluated. Integrations produce this by mapping an internal CRM value straight through.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set entityType to one of the five values exactly, in upper case with underscores. Done when the submitted string is character-identical to one of them.",
      "example": "entity_type: PRIVATE_PROFIT",
      "pitfalls": [
        "LLC, Corporation and Partnership are legal forms rather than entity types — nearly all of them register as PRIVATE_PROFIT."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-041",
      "slug": "brd-041",
      "title": "The entity type must match the classification on the tax record",
      "statement": "The selected entity type must match how the business is classified on its tax record, with documentation where the classification is not machine-verifiable.",
      "rationale": "Entity type drives which verification path runs and which use cases open, so it is checked against the tax classification rather than taken on trust. Honest mismatches are everywhere: an LLC taxed as an S-corp, a member-owned co-operative, a non-profit that never completed its exemption. The registrant picks the description that feels true and the record says something else.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type vs the tax record",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30796",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30797",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30798",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Match the entity type to the classification on the IRS letter rather than to how the business describes itself. Nearly every commercial company — LLC, S-corp, C-corp, partnership — is PRIVATE_PROFIT. Where the classification is genuinely unusual, attach the determination letter with the registration.",
      "notes": "The tax record is not something we can read. The user has to check their CP-575 or determination letter and pick the entity type that matches it — and where the two genuinely differ, attach the document rather than arguing the point in an appeal, because the reviewer is comparing against the same record.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the entity type you selected match the classification on the IRS letter, rather than how the business describes itself?",
        "howToCheck": [
          "Read the classification on the CP-575 or the determination letter.",
          "Nearly every commercial company — LLC, S-corp, C-corp, partnership — is PRIVATE_PROFIT, whatever it calls itself internally.",
          "Where the classification is genuinely unusual (a co-operative, a mutual association), attach the determination letter with the registration."
        ],
        "failureLooksLike": "An LLC taxed as an S-corp picks the description that feels most accurate, and the record says something else. The mismatch fails the verification path rather than merely re-grading it."
      }
    },
    {
      "id": "BRD-042",
      "slug": "brd-042",
      "title": "A non-US organisation registers as private for profit",
      "statement": "A brand whose country of registration is not the US must be PRIVATE_PROFIT — GOVERNMENT and NON_PROFIT are US-only classifications.",
      "rationale": "The non-profit and government paths verify against US registers — the IRS exempt-organisation file, US government domains — so a foreign charity selecting NON_PROFIT is checked against a database it cannot appear in and fails for looking fraudulent rather than foreign. Registering as PRIVATE_PROFIT is not a demotion; it is the only classification the verification chain can actually resolve.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type + brand.country",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set entityType to PRIVATE_PROFIT and supply the home country's business registration number. Done when the entity type is PRIVATE_PROFIT and the identifier is the one that country issues.",
      "example": "entity_type: PRIVATE_PROFIT · country: GB · identifier: the UK company number",
      "pitfalls": [
        "A foreign non-profit that holds a US EIN can register as NON_PROFIT on the strength of it — the test is the US tax record, not where the charity operates."
      ],
      "notes": "Also discharges BRD-052, which states the same requirement from the non-profit side: NON_PROFIT is available only to a US-based organisation or one holding a US EIN. The check already carries that exception, so the two are one rule rather than two.",
      "catalogIds": [
        "BRD-052"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-043",
      "slug": "brd-043",
      "title": "The business-type field takes its own enum, not the entity type",
      "statement": "Where a business_type is declared, it must be one of Co-operative, Corporation, Limited Liability Corporation, Non-profit Corporation or Partnership.",
      "rationale": "This is a second classification field with a different vocabulary from entityType, on the same form, describing the same company. Populating it from the entity type — PRIVATE_PROFIT, NON_PROFIT — produces values that are not in its enum, and the submission fails validation on a field the user believed they had already answered further up the page.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set business_type to the legal form of the company — an LLC is \"Limited Liability Corporation\", an Inc is \"Corporation\" — and leave entityType as the TCR classification. Done when the two fields hold values from their own lists rather than from each other's.",
      "example": "business_type: Limited Liability Corporation · entity_type: PRIVATE_PROFIT — for Acme Coffee Co, LLC",
      "notes": "Twilio Trust Hub carries this field and TCR does not, so it is checked only where it is populated: a registration that never declares one passes. Where the submission target is Twilio the field is mandatory, and the check says so rather than passing silently.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-044",
      "slug": "brd-044",
      "title": "Changing an identity field re-runs verification, and costs the fee again",
      "statement": "Any change to entity type, EIN, EIN issuing country or legal name re-runs brand verification and resets the identity status.",
      "rationale": "These four fields are the identity itself, so editing one is not a correction to a verified brand — it is a new claim about who the business is, and the registry treats it that way: the status drops back, the fee is charged again, and any completed vet expires with it. Teams fixing a typo in a legal name are routinely surprised to find a working brand back in UNVERIFIED and their campaigns blocked behind it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type + brand.ein + brand.ein_issuing_country + brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Vonage",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Batch every identity correction into one edit rather than making them one at a time — each round trip charges the verification fee again. Expect the brand to return to UNVERIFIED and campaign registration to be blocked until it clears, and do not schedule the change on the day of a send.",
      "notes": "Absorbs OPS-343, which states the same trigger together with its two consequences: the identity reset, and the vet expiry that BRD-222 covers. The re-verification itself happens at the registry and takes as long as it takes — what the user has to do is time the change deliberately and be ready to re-order any external vet that expires with it.",
      "catalogIds": [
        "OPS-343"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Are all of your identity corrections — entity type, EIN, EIN country, legal name — batched into a single edit, made on a day you are not sending?",
        "howToCheck": [
          "List every identity field you intend to change and make them in one operation. Each separate round trip charges the verification fee again.",
          "Expect the brand to drop to UNVERIFIED and campaign registration to be blocked until it clears, and expect any completed external vet to expire with it."
        ],
        "failureLooksLike": "A typo in the legal name is fixed on a Friday. A working brand is back in UNVERIFIED, its vet has expired, its campaigns are blocked, and the fee has been charged twice."
      }
    },
    {
      "id": "BRD-045",
      "slug": "brd-045",
      "title": "PUBLIC_PROFIT means the shares actually trade",
      "statement": "The PUBLIC_PROFIT classification is available only to a company whose shares are genuinely publicly traded.",
      "rationale": "Public companies start at the top carrier trust class and get the throughput that comes with it, so the claim is checked against an exchange listing rather than taken from the form. The mistake is usually a large private company reading \"public\" as \"well known\", and the result is not a downgrade — it is a failed identity verification, because the ticker matched nothing.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30796",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Register as PRIVATE_PROFIT unless the company has a ticker on a recognised exchange. A private company with high volume gets there through external vetting instead, which raises throughput on evidence rather than on classification.",
      "notes": "The listing is checked against exchange data we do not hold. What the user has to confirm themselves is that the company files with the SEC and its shares trade under the symbol being submitted — a company that was public and has since been taken private is the case that catches people, because the ticker is still remembered.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do this company's shares trade today, under the symbol on this brand, on a recognised exchange?",
        "howToCheck": [
          "Look the ticker up and confirm it returns this company — not a similarly named one, and not a listing that has since been withdrawn.",
          "Confirm the company still files with the SEC. A company taken private keeps its remembered ticker and loses the classification."
        ],
        "failureLooksLike": "A large, well-known private company reads \"public\" as \"well known\". The result is not a downgrade to private — it is a failed identity verification, because the ticker matched nothing."
      }
    },
    {
      "id": "BRD-047",
      "slug": "brd-047",
      "title": "Public companies must supply stock symbol and exchange",
      "statement": "A PUBLIC_PROFIT brand must declare both its ticker symbol and the exchange it trades on.",
      "rationale": "Public companies get elevated trust and throughput, and the ticker is what lets a vetting provider confirm the claim against an exchange listing. Without both symbol and exchange the claim cannot be checked, so it is refused.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.stock_symbol + brand.stock_exchange",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Enter the ticker symbol and the exchange (e.g. NASDAQ, NYSE).",
      "example": "Stock symbol: ACME · Exchange: NASDAQ",
      "catalogIds": [
        "BRD-046"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-048",
      "slug": "brd-048",
      "title": "The stock exchange must be a recognised enum value",
      "statement": "A public company's stock exchange must be one of the supported registry values.",
      "rationale": "The exchange and symbol together are how a public brand is matched to a listing, so a free-text exchange name matches nothing and drops the brand out of the public-company path it was registered for. People type what they say aloud — \"Nasdaq\", \"New York Stock Exchange\" — rather than the code the enum wants.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.stock_exchange",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Telnyx"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Pick the exchange code from the registry list rather than typing the name. Done when the value is one of the short codes — NASDAQ, NYSE, LON, TSX and the rest — in upper case.",
      "example": "stock_exchange: NASDAQ · stock_symbol: ACME",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-049",
      "slug": "brd-049",
      "title": "Stock fields must be empty for non-public entities",
      "statement": "stockSymbol and stockExchange must be omitted entirely when the entity type is not PUBLIC_PROFIT.",
      "rationale": "A populated ticker on a private company is an internally contradictory registration, and vetting treats contradictions as failed identity verification rather than as a harmless extra field. It usually happens when a form is copied from a template or a previous registration.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.stock_symbol + brand.stock_exchange",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a public company.",
      "universal": false,
      "remediation": "Clear both stock fields. They apply only to publicly traded companies.",
      "example": "Stock symbol: (empty) · Stock exchange: (empty)",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-050",
      "slug": "brd-050",
      "title": "A stock symbol may back only one brand",
      "statement": "A stock symbol already registered against another brand cannot be used again.",
      "rationale": "The ticker is treated as an identity key, so two brands claiming the same symbol is read as one of them being wrong. Large groups hit this legitimately: a subsidiary registering under the parent's ticker collides with the parent's own brand, and the subsidiary has no visibility of it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.stock_symbol",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30703",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Register the subsidiary under its own entity type and identifier rather than the listed parent's ticker. Where the listed entity itself is being registered twice, share or transfer the existing brand instead of creating a second one.",
      "notes": "The collision is against every brand in the registry, which we cannot see. The user has to establish whether anyone in their group — or a former vendor — has already registered a brand under this ticker, and if so route the traffic through that brand rather than duplicating it.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has anyone else in your group, or a former vendor, already registered a brand under this ticker?",
        "howToCheck": [
          "Ask the parent company's messaging or IT owner whether a 10DLC brand already exists for the listed entity.",
          "Where one does, route this traffic through the existing brand rather than creating a second one; a subsidiary registers under its own entity and identifier, not the listed parent's ticker."
        ],
        "failureLooksLike": "A subsidiary registers under the parent's ticker and collides with the parent's own brand — a record it has no visibility of and cannot query."
      }
    },
    {
      "id": "BRD-051",
      "slug": "brd-051",
      "title": "Russell 3000 membership sets the starting carrier class",
      "statement": "A verified public brand on the Russell 3000 starts at the top carrier classes; one that is not starts at the bottom until it is vetted.",
      "rationale": "The same PUBLIC_PROFIT classification produces very different throughput depending on an index membership nobody is asked about, so two public companies register identically and one can send a hundred times more than the other. Knowing which side you are on before launch is the difference between planning an external vet and discovering mid-campaign that the throughput is not there.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name + the Russell 3000 list",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "T-Mobile",
        "AT&T",
        "Infobip",
        "Bandwidth"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Check whether the company is a current Russell 3000 constituent. If it is not, plan an external vet into the launch schedule rather than assuming the public classification is enough — the starting class without one is the lowest tier, not the highest.",
      "notes": "Informational rather than a rejection: nothing fails on this. The user checks the current constituent list themselves — it is published annually and reconstituted each June — and uses the answer to decide whether to buy a vet before launch.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is this company a current Russell 3000 constituent?",
        "howToCheck": [
          "Check the published constituent list — it is reconstituted each June, so a company can drop off without anyone noticing.",
          "If it is not on the list, plan an external vet into the launch schedule: the starting class without one is the lowest tier, not the highest."
        ],
        "failureLooksLike": "Two public companies register identically and one can send a hundred times more than the other. Nothing fails; the throughput simply is not there when the campaign goes live."
      }
    },
    {
      "id": "BRD-053",
      "slug": "brd-053",
      "title": "Tax-exempt status must be verified against the IRS registers",
      "statement": "A NON_PROFIT registration must be backed by an EIN that appears in the IRS Tax-Exempt Organization Search.",
      "rationale": "The non-profit classification and the charity use case both rest on a status the IRS publishes and anyone can check, so the claim is verified rather than accepted. Organisations that operate not for profit but never completed a 501(c) application — community groups, mutual associations, clubs — are outside the register and are refused for a status they never actually held, which is a difficult conversation to have after launch.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "Plivo",
        "HighLevel"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30798",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "NON_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a non-profit.",
      "universal": false,
      "remediation": "Search the IRS Tax-Exempt Organization Search for the EIN before submitting, and register as PRIVATE_PROFIT if it does not appear. Where the exemption is genuine but the register is out of date, expect to attach the determination letter to an appeal.",
      "notes": "Absorbs BRD-061, which is the same lookup used to establish a TCPA exemption rather than an entity type. We hold no IRS data, so the user runs the search themselves — it is free and public — and should do it before choosing the entity type rather than after the rejection, because switching entity type re-runs verification (BRD-044).",
      "catalogIds": [
        "BRD-061"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does this EIN appear in the IRS Tax-Exempt Organization Search?",
        "howToCheck": [
          "Search the EIN at the IRS Tax-Exempt Organization Search. It is free and public.",
          "If it does not appear, register as PRIVATE_PROFIT — and do it before submitting, because changing entity type afterwards re-runs verification and charges the fee again.",
          "Genuinely exempt but absent from the register: expect to attach the determination letter to an appeal."
        ],
        "failureLooksLike": "A community group, mutual association or club that operates not for profit but never completed a 501(c) application is refused for a status it never actually held — a hard conversation to have after launch."
      }
    },
    {
      "id": "BRD-055",
      "slug": "brd-055",
      "title": "Non-profit classification must be consistent across every field",
      "statement": "A non-profit brand must be marked as non-profit consistently across entity type, vertical, and any company-type field.",
      "rationale": "The three fields feed different downstream systems, and a brand marked NON_PROFIT with a commercial vertical produces a registration that contradicts itself. Charity and political use-case eligibility both key off this classification, so an inconsistency blocks the use cases the organisation actually needs.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type + brand.vertical",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Infobip"
      ],
      "applicability": {
        "entityTypes": [
          "NON_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a non-profit.",
      "universal": false,
      "remediation": "Set the vertical to the non-profit / NGO value alongside the NON_PROFIT entity type, and declare the tax-exempt status on the brand record.",
      "example": "Entity type: NON_PROFIT · Vertical: NGO · Tax exempt status: 501(c)(3)",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-057",
      "slug": "brd-057",
      "title": "A charity brand needs an arm's-length accreditation",
      "statement": "A brand running the charity use case must be accredited by at least one independent non-profit accreditation body.",
      "rationale": "The charity use case carries relief that ordinary marketing does not get, and tax-exempt status alone is a low bar — it says the IRS accepted a form, not that the organisation is what it says. An accreditation from a body that examines finances and governance is the evidence carriers actually want, and a charity that has never sought one finds this out at the point of registration rather than at the point of applying.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "accreditation listing URL",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Obtain a current BBB Wise Giving Alliance report or a current three- or four-star Charity Navigator rating, and keep the listing URL with the registration. Where neither is held, register the messaging under a use case that does not claim charitable standing until accreditation is in place.",
      "notes": "We cannot check an accreditation listing. The user has to hold one before registering: the BBB report takes weeks to obtain and Charity Navigator ratings are computed from filed returns, so neither is available on demand — this is a lead-time item, not a form field.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you hold a current BBB Wise Giving Alliance report, or a current three- or four-star Charity Navigator rating, with its listing URL to hand?",
        "howToCheck": [
          "Look the organisation up on both and note the listing URL for whichever it holds.",
          "Check the rating is current rather than historical — Charity Navigator ratings are computed from filed returns and lapse.",
          "Neither in place: this is a lead-time item. A BBB report takes weeks, so register under a use case that does not claim charitable standing until it lands."
        ],
        "failureLooksLike": "Tax-exempt status is treated as sufficient. It says the IRS accepted a form, not that an independent body examined the finances — which is the evidence carriers actually want."
      }
    },
    {
      "id": "BRD-058",
      "slug": "brd-058",
      "title": "Keep the charity evidence bundle together",
      "statement": "A charity brand must retain its organisation name, EIN, charity website and accreditation listing URL as proof of 501(c)(3) standing.",
      "rationale": "The charity claim is re-examined whenever a campaign under it is reviewed, and the four items are asked for together because each covers a different substitution — a real charity's name with someone else's EIN, a real EIN with a lookalike website. Charities that assembled the evidence once for the original submission and did not keep it end up rebuilding it under time pressure during a review.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "charity evidence bundle",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "T-Mobile",
        "Bandwidth",
        "Infobip"
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Keep one folder holding the registered organisation name, the EIN, the charity website URL and the accreditation listing URL, and keep it current — a lapsed Charity Navigator rating invalidates the bundle. Done when all four can be produced in a single reply to a review request.",
      "notes": "Evidence only the organisation holds. What the user must do is assemble the four items now and store them where whoever answers a carrier review can reach them, rather than relying on the person who filed the registration still being there.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Can one person produce all four items — registered organisation name, EIN, charity website, accreditation listing URL — in a single reply to a carrier review?",
        "howToCheck": [
          "Put the four in one folder where whoever answers a review can reach it, not with the person who happened to file the registration.",
          "Re-check the accreditation listing periodically: a lapsed Charity Navigator rating invalidates the bundle."
        ],
        "failureLooksLike": "The evidence was assembled once for the original submission and never kept. A campaign review arrives with a deadline and the bundle is rebuilt under time pressure."
      }
    },
    {
      "id": "BRD-059",
      "slug": "brd-059",
      "title": "A unified fundraising short code needs five years of operating history",
      "statement": "A charity seeking a single unified short code for fundraising must demonstrate at least five years of operation.",
      "rationale": "Unified fundraising codes are shared infrastructure with a high abuse value — a fraudulent appeal on a recognised code damages every charity on it — so the entry bar is time in operation rather than paperwork. Newer charities can still message; what they cannot do is join the unified code, and planning a campaign around one is the expensive way to discover that.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "charity operating history",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Plan the campaign on a 10DLC number or a dedicated short code where the organisation is younger than five years. Revisit the unified code once the operating history is there; nothing about the registration changes in the meantime.",
      "notes": "A short-code path requirement rather than a 10DLC one, recorded here because charities plan across both. The user has to check their own incorporation date against the five-year bar before committing to a unified-code campaign — no part of the 10DLC registration will tell them.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has this organisation been operating for at least five years, counting from its incorporation date?",
        "howToCheck": [
          "Find the incorporation or determination date on the organisation's founding documents.",
          "Under five years, plan the campaign on a 10DLC number or a dedicated short code and revisit the unified fundraising code later — nothing about the registration changes in the meantime."
        ],
        "failureLooksLike": "A four-year-old charity builds its whole appeal around a unified short code, and finds out at the point of application that the bar is time in operation and there is no paperwork that shortens it."
      }
    },
    {
      "id": "BRD-060",
      "slug": "brd-060",
      "title": "A 501(c)(3) may not run certain use cases",
      "statement": "A brand holding 501(c)(3) status must not register the Agents & Franchises, Carrier Exempt, Proxy, Social or Sweepstake use cases.",
      "rationale": "These five use cases are commercial mechanics — reselling reach, sweepstakes promotion, proxy messaging — and a charitable exemption is granted on the basis that the organisation is not doing them. The block protects the exemption as much as the channel: a charity that runs a sweepstakes campaign on its exempt status has a problem considerably larger than a rejected registration.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.tax_exempt_status + campaign.use_case",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "NON_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a non-profit.",
      "universal": false,
      "remediation": "Choose a use case the exemption permits — CHARITY for fundraising and appeals, ACCOUNT_NOTIFICATION for supporter records, CUSTOMER_CARE for enquiries. If the programme really is a sweepstake or an agent network, it belongs on a separate commercial brand rather than on the exempt entity. Done when the use case is one a 501(c)(3) may hold.",
      "example": "tax_exempt_status: 501(c)(3) · use_case: CHARITY — not SWEEPSTAKE",
      "notes": "Decidable from the two declared values, which is why it carries a check even though the catalog grades it EXTERNAL_DATA — the external half is confirming the 501(c)(3) status itself, which is BRD-053. The neighbouring rule XBC-002 asks the opposite question: whether a brand claiming the CHARITY use case actually holds the status.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is the 501(c)(3) status on this brand real and current, and is the use case one an exempt organisation may hold?",
        "howToCheck": [
          "Confirm the exemption in the IRS Tax-Exempt Organization Search.",
          "Check the use case is not Agents & Franchises, Carrier Exempt, Proxy, Social or Sweepstake — those five are commercial mechanics an exemption is granted on the basis of not doing."
        ],
        "failureLooksLike": "A charity runs a sweepstake campaign on its exempt status. The rejected registration is the smallest of its problems."
      }
    },
    {
      "id": "BRD-062",
      "slug": "brd-062",
      "title": "GOVERNMENT is for US government bodies only",
      "statement": "The GOVERNMENT entity type is reserved for US federal, state or local government bodies.",
      "rationale": "The government classification carries the top carrier trust class and the throughput that comes with it, so it is verified strictly and misuse is treated as an attempt to obtain that standing. Contractors, public utilities and government-funded non-profits reach for it reasonably and are all outside it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "applicability": {
        "entityTypes": [
          "GOVERNMENT"
        ]
      },
      "applicabilityText": "Applies when the brand is a government.",
      "universal": false,
      "remediation": "Register as GOVERNMENT only if the entity is itself a US federal, state, county or municipal body. A contractor or a grant-funded organisation registers under its own classification — usually PRIVATE_PROFIT or NON_PROFIT. Done when the entity type matches what the organisation legally is.",
      "example": "entity_type: GOVERNMENT · country: US — a city, county, state agency or federal department",
      "notes": "Only the country half is checkable here. Whether the organisation IS a government body is EXTERNAL_DATA, verified against a .gov domain and the vetting report, so a US-country GOVERNMENT brand passes this rule and may still fail vetting.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-064",
      "slug": "brd-064",
      "title": "A government brand must carry the government attribute to get its class",
      "statement": "A GOVERNMENT brand must have the governmentEntity attribute set before the top carrier class can be assumed.",
      "rationale": "The entity type and the attribute are separate: registering as GOVERNMENT selects the classification, but the throughput comes from an optional attribute that verification sets — and when it does not, the brand sits at the bottom carrier class with no error anywhere. Agencies plan capacity on the class they expected and find out on the day of a mass notification, which is the worst possible moment for a government sender.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.optional_attributes.government_entity",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AT&T",
        "T-Mobile"
      ],
      "applicability": {
        "entityTypes": [
          "GOVERNMENT"
        ]
      },
      "applicabilityText": "Applies when the brand is a government.",
      "universal": false,
      "remediation": "Check the brand record for the governmentEntity attribute after verification completes. If it is missing, file an appeal in the \"Verify Government status\" category with the authorising statute or the agency's .gov domain as evidence rather than re-registering. Done when the attribute reads true on the brand.",
      "example": "entity_type: GOVERNMENT · government_entity: true — both, not just the entity type",
      "pitfalls": [
        "The attribute is what carriers read, not the entity type, so a GOVERNMENT brand without it is treated as an ordinary standard brand however it was registered."
      ],
      "notes": "BRD-263 is the appeal route when this attribute is missing on an otherwise VERIFIED brand; this rule is the detection.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-065",
      "slug": "brd-065",
      "title": "Sole proprietor must have no EIN",
      "statement": "The SOLE_PROPRIETOR tier is for a US individual without an EIN. Supplying an EIN disqualifies the registration.",
      "rationale": "The sole proprietor tier is defined as the path for an individual WITHOUT an EIN. Supplying one is a contradiction the platform reads as a misfiled standard brand, and it is corrected by changing tier rather than by editing the field.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30903",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30915",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "If the business has an EIN, register it as a standard brand (Private/Public/Non-profit) instead of Sole Proprietor. If it genuinely has no EIN, clear the EIN field.",
      "example": "EIN: (leave empty)",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-067",
      "slug": "brd-067",
      "title": "A business trading under a DBA belongs on the standard tier",
      "statement": "A sole-proprietor registration that declares a DBA must be re-filed as a standard brand.",
      "rationale": "The sole-proprietor tier is for an individual with no business registration at all. A DBA is a business registration — it is filed with a county or state — so declaring one is evidence the applicant qualifies for, and therefore must use, the standard tier. The tiers are not interchangeable: the sole-proprietor path caps throughput hard and verifies by text message, which is a worse deal than the applicant is entitled to.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.dba + brand.entity_type",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30914",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30918",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Apply for an EIN — it is free and issued immediately online — and register as a standard brand with the DBA in its own field. If you would rather stay on the sole-proprietor tier, clear the DBA and register under your own name only. Done when the tier and the presence of a DBA agree.",
      "example": "entity_type: PRIVATE_PROFIT · company_name: Jane Doe Bodywork, LLC · dba: Jane Doe Bodywork — rather than the sole-proprietor tier with a DBA attached",
      "pitfalls": [
        "Waiting for a new EIN to propagate is BRD-027 and costs up to 90 days, so apply for it before you need the brand rather than after this rejection."
      ],
      "notes": "A different disqualifier from BRD-065 (an EIN present on a sole-proprietor brand) with a different fix, which is why the catalog keeps them apart.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-068",
      "slug": "brd-068",
      "title": "Sole proprietor brand name must be a person, not a company",
      "statement": "The sole proprietor brand name must be the individual’s first and last name — never an LLC, Inc, Corp or other registered entity name.",
      "rationale": "A sole proprietor is a natural person, so the brand name is a person's name. A corporate suffix proves a registered entity exists behind it, which means the business qualifies for — and is required to use — the standard tier instead.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30914",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Enter the individual’s legal first and last name. A business name with a corporate suffix means the entity is not a sole proprietor for 10DLC purposes — register it as a standard brand.",
      "example": "Brand name: Jane Doe",
      "catalogIds": [
        "BRD-069"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-070",
      "slug": "brd-070",
      "title": "The verification number must be a real US or Canadian mobile",
      "statement": "The sole-proprietor mobile number must be an active US or Canadian wireless number in E.164 that can receive SMS.",
      "rationale": "This number is the entire identity check on this tier — there is no tax record behind it — so it has to be a handset a person answers, not a landline, a VoIP line, or a number from the platform being registered on. A business owner who supplies their office number gets no OTP, no error, and a brand that stays UNVERIFIED with nothing on the form to explain it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.mobile_phone",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "551",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30751",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Supply the owner's personal mobile in E.164 — +1 followed by ten digits — and have the handset to hand when the registration is submitted. Send yourself a test text first if you are unsure the line receives SMS.",
      "notes": "The line type is a carrier lookup we cannot run. What the user has to confirm themselves is that the number is a genuine wireless line on a US or Canadian carrier: office VoIP numbers, Google Voice numbers and numbers issued by a CPaaS all look correct in the field and none of them will complete the OTP.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the verification number a genuine wireless line on a US or Canadian consumer carrier, able to receive SMS today?",
        "howToCheck": [
          "Text the number from another phone and confirm it arrives on a handset.",
          "Rule out the three lookalikes: an office VoIP line, a Google Voice number, and a number issued by a messaging provider. All three look correct in the field and none will complete the OTP.",
          "Enter it in E.164 — +1 followed by the ten digits."
        ],
        "failureLooksLike": "The owner supplies the office number. No OTP arrives, no error is shown, and the brand sits UNVERIFIED with nothing on the form to explain why."
      }
    },
    {
      "id": "BRD-072",
      "slug": "brd-072",
      "title": "Sole proprietor mobile number has a reuse limit",
      "statement": "A mobile number used for sole proprietor OTP verification may back at most three brand registrations, and must not be a CPaaS-issued number.",
      "rationale": "The verified mobile number is the only real identity check the sole proprietor tier has, so its reuse is capped and CPaaS-issued numbers are refused — otherwise one operator could mint unlimited \"individuals\". The 24-hour reply window is short and unforgiving.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.phone",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30750",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30751",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30752",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Use a personal mobile number that can receive the verification text, is not issued by a CPaaS provider, and has not already backed three registrations. The owner must reply YES within 24 hours.",
      "notes": "Off-platform: the reuse count and the CPaaS origin of a number both live in TCR, which we cannot query, so this rule can only ever warn. The user has to confirm for themselves that the number is a real personal handset and has not already backed three brands, and then be holding it when the OTP arrives — the 24-hour window starts at submission and cannot be restarted without a new registration.",
      "catalogIds": [
        "BRD-071"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the verification number a personal handset the owner will have with them, not issued by a messaging provider, and used on fewer than three brands already?",
        "howToCheck": [
          "Confirm the number is on a consumer mobile carrier — not one bought from Twilio, Telnyx, Bandwidth or any messaging platform, including the console you are registering in.",
          "Ask the owner how many sole proprietor brands they have registered on this number. Three is the ceiling across the whole registry.",
          "Make sure the owner is expecting the verification text and can reply YES within 24 hours of submission."
        ],
        "failureLooksLike": "The number was provisioned in the same console being used to register, so it verifies as CPaaS-issued and is refused; or it is a fourth registration, which fails against a count nobody outside TCR can see."
      }
    },
    {
      "id": "BRD-075",
      "slug": "brd-075",
      "title": "The verification text must be answered within 24 hours",
      "statement": "The sole-proprietor OTP must be answered YES within 24 hours, or the verification must be re-triggered.",
      "rationale": "The window is short and it starts at submission, not when someone gets round to looking. A brand whose OTP expires does not fail loudly — it sits UNVERIFIED, and every campaign under it is refused for a reason that names the campaign rather than the unanswered text from yesterday. Registrations filed on a Friday afternoon are the classic version.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "sole-proprietor OTP state",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "552",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30752",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Submit the registration while the owner is at their phone, and tell them a text is coming that needs a YES. If the window has already passed, re-trigger the OTP from the brand record rather than creating a second brand — a duplicate registration burns one of the three uses this number is allowed.",
      "notes": "Nothing on our side can see whether the text was answered. The user has to watch for it: the message arrives within a minute or two of submission, the reply must be exactly YES, and after 24 hours the only route is to re-trigger — which is a button on the existing brand, not a resubmission.",
      "catalogIds": [
        "OPS-314"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Will the number's owner be at their phone when you submit, and do they know to reply YES?",
        "howToCheck": [
          "Tell the owner before you submit: a text arrives within a minute or two, and the reply must be exactly YES.",
          "Submit while they are available — not on a Friday afternoon. The 24-hour window starts at submission, not when someone gets round to looking.",
          "If the window has passed, re-trigger the OTP from the existing brand. Do not create a second brand: that burns one of the three uses this number is allowed."
        ],
        "failureLooksLike": "The text expires unanswered. Nothing fails loudly — the brand sits UNVERIFIED and every campaign under it is refused for a reason that names the campaign."
      }
    },
    {
      "id": "BRD-076",
      "slug": "brd-076",
      "title": "Changing the mobile number re-runs the verification",
      "statement": "Any change to the sole-proprietor mobile number reverts the brand to UNVERIFIED and requires the OTP to be run again.",
      "rationale": "The number is the identity, so replacing it is a new identity claim rather than a contact-details update — the status drops and campaigns under the brand stop being registrable until a fresh YES arrives. People change it for entirely ordinary reasons, usually because they switched phones or mistyped a digit, and nothing warns them that a verified brand is about to go back in the queue.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.mobile_phone",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Make the change with the new handset in hand, and expect a fresh verification text immediately. Do not change the number while a campaign is being reviewed — the brand will drop to UNVERIFIED underneath it.",
      "notes": "We can see that the number is being changed; we cannot see whether the new OTP was answered, which is BRD-075. What the user has to do is treat this as a re-verification rather than an edit, and schedule it accordingly.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is the new handset in the owner's hand right now, and is there no campaign under review?",
        "howToCheck": [
          "Treat this as a re-verification rather than a contact-details edit: the brand drops to UNVERIFIED the moment the number changes.",
          "Make the change with the owner present, expect the verification text immediately, and check no campaign is mid-review — it will be judged against an unverified brand."
        ],
        "failureLooksLike": "A digit is corrected on a verified brand mid-week. The status drops, campaign registration stops, and nothing warned that a working brand was going back into the queue."
      }
    },
    {
      "id": "BRD-081",
      "slug": "brd-081",
      "title": "A sole-proprietor brand cannot buy its way to more throughput",
      "statement": "External vetting, and the throughput increases vetting buys, are not available on a sole-proprietor brand.",
      "rationale": "Vetting works by confirming a business against commercial records, and this tier exists precisely for people who are not in those records — so the vet has nothing to verify and the order is refused rather than failed. It matters because the obvious response to a low throughput cap is to buy a vet, and here that spends a non-refundable fee on an operation that was never possible.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "external vet order",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30715",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "525",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Do not order a vet on this brand. If the throughput cap is the problem, the route up is an EIN and a standard brand, which can then be vetted — the tier change is the increase, and the vet only helps once you are on the other tier.",
      "example": "vetting: none on a sole-proprietor brand — the throughput increase comes from moving to a standard brand instead",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-082",
      "slug": "brd-082",
      "title": "RCS brand assets are not available on the sole-proprietor tier",
      "statement": "Logo and banner assets for RCS cannot be uploaded to a sole-proprietor brand.",
      "rationale": "RCS shows a verified sender card with the brand's own artwork, and that card is a trust signal underwritten by the verification behind it — which on this tier is a text message to a phone. The upload is refused rather than queued, so a business that built its RCS artwork first discovers the channel is closed to it after the design work is done.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.assets",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "592",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Remove the asset upload from this registration. If RCS is the goal, obtain an EIN and register a standard brand — RCS asset upload and the verified sender card both hang off the standard verification path.",
      "example": "brand assets: none on the sole-proprietor tier — SMS and MMS only",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-084",
      "slug": "brd-084",
      "title": "Sole proprietor is not supported by every provider",
      "statement": "Some providers refuse sole proprietor registrations outright, and one also refuses sole proprietor appeals.",
      "rationale": "Provider support for the sole proprietor tier genuinely differs: TCR, Twilio, Telnyx and Vonage support it while Bandwidth and Sakari refuse it, and Bandwidth also refuses sole proprietor appeals. Discovering this after collecting the customer's data wastes a non-refundable submission.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Bandwidth",
        "Sinch"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ],
        "providers": [
          "BANDWIDTH",
          "SINCH"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor, and the submission target is BANDWIDTH and SINCH.",
      "universal": false,
      "remediation": "This provider does not accept sole proprietor brands. Either register through a provider that supports the tier, or obtain an EIN and register as a standard brand.",
      "notes": "PROVIDER-SPECIFIC — a genuine divergence, not a strictness difference. TCR, Twilio, Telnyx and Vonage all support the tier; Bandwidth and Sakari/Sinch refuse it. Tagged so it only fires when we know the target.",
      "catalogIds": [
        "OPS-340"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you confirmed that the provider you are submitting through accepts sole proprietor brands at all?",
        "howToCheck": [
          "Read the provider's own 10DLC onboarding documentation for the sole proprietor or low-volume tier.",
          "Where it is not stated either way, ask support in writing before you collect the customer's personal details. Bandwidth and Sinch refuse the tier outright, and Bandwidth also refuses sole proprietor appeals."
        ],
        "failureLooksLike": "The API accepts the registration and it is refused downstream with a generic error, after a private individual's mobile number and home address have already been collected."
      }
    },
    {
      "id": "BRD-086",
      "slug": "brd-086",
      "title": "Sole-proprietor throughput is capped, and the CSP enforces it",
      "statement": "Sole-proprietor traffic is capped at roughly 1,000 messages a day and 15 a minute, enforced at the CSP rather than by the carrier.",
      "rationale": "Because the cap is applied upstream of the carrier, exceeding it does not produce message-level rejections — traffic is throttled or the account is reviewed, which looks like a delivery problem rather than a limit. A business planning a launch send on this tier will not find the ceiling in any error code, only in queue times that get steadily worse.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "sole-proprietor send rate",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "T-Mobile",
        "AT&T"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Size the programme against roughly 1,000 messages a day and 15 a minute before committing to a schedule. If a single send would exceed it, spread it across days or move to a standard brand — there is no increase available on this tier.",
      "notes": "Send rate is not visible at registration time. The user has to do the arithmetic themselves — list size against the daily cap — and knowing that the CSP applies it means the symptom to watch for is throttling, not rejections.",
      "catalogIds": [
        "OPS-125"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your largest planned send fit inside roughly 1,000 messages a day and 15 a minute?",
        "howToCheck": [
          "Do the arithmetic before committing to a schedule: list size against the daily cap, and burst rate against 15 a minute.",
          "If a single send would exceed it, spread it across days or move to a standard brand. There is no increase available on this tier.",
          "Watch for throttling rather than rejections — the cap is applied at the CSP, upstream of the carrier."
        ],
        "failureLooksLike": "No error code names the ceiling. Queue times get steadily worse, the launch send arrives over two days, and it reads as a delivery problem rather than a limit."
      }
    },
    {
      "id": "BRD-087",
      "slug": "brd-087",
      "title": "Monthly sole-proprietor traffic reports are due by the 7th",
      "statement": "Where a CSP is enabled for sole-proprietor brands, monthly traffic reports must reach TCR by the 7th of the following month.",
      "rationale": "The tier was granted to CSPs on condition that its traffic is reported, so the filing is what keeps the tier available rather than a formality — a CSP that stops filing puts every sole-proprietor brand it holds at risk, not just its own standing. Businesses on the tier never see this obligation, which is exactly why it is worth their CSP knowing it exists.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "sole-proprietor traffic report",
      "severity": "HIGH",
      "detectability": [
        "HUMAN",
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the filing on a recurring calendar entry for the first working days of each month, with the previous month's sole-proprietor volumes. Done when the report is accepted by TCR before the 7th.",
      "notes": "An obligation on the CSP rather than on the brand, and nothing in a registration reveals whether it is being met. What the user has to establish is whether their provider is SP-enabled and files these reports — a provider that quietly stops can lose the tier, and every sole-proprietor brand behind it goes with it.",
      "catalogIds": [
        "OPS-317"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is your provider enabled for sole-proprietor brands, and does it file the monthly traffic report to TCR by the 7th?",
        "howToCheck": [
          "Ask the provider directly whether they are SP-enabled and who owns the monthly filing.",
          "If you are the CSP, put the filing on a recurring calendar entry for the first working days of each month."
        ],
        "failureLooksLike": "A provider quietly stops filing and loses the tier. Every sole-proprietor brand behind it goes too, and none of those businesses ever knew the obligation existed."
      }
    },
    {
      "id": "BRD-090",
      "slug": "brd-090",
      "title": "A sole-proprietor brand cannot move between providers",
      "statement": "Sole-proprietor brands cannot be migrated from one CSP to another.",
      "rationale": "Migration carries a brand's verified status across, and on this tier that status is a verification of a person that the receiving CSP did not perform — so the path simply does not exist. Anyone changing provider has to re-register and answer a fresh OTP, which is survivable if it is planned and disruptive if it is discovered during the switch.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand migration request",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Plan a provider change as a new registration: a new brand, a new campaign, and a fresh verification text to the same handset. Keep the old brand running until the new one is verified, and expect the number reuse to count against the three-registration limit.",
      "notes": "The migration is refused at the registry, not by us. What the user has to do is sequence the change themselves — stand the new brand up before retiring the old one — because there is no transfer to fall back on if the new registration stalls.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "If you are changing provider, have you planned this as a fresh registration rather than a transfer?",
        "howToCheck": [
          "Confirm there is no migration path for this tier — there is not — and plan a new brand, a new campaign and a fresh OTP to the same handset.",
          "Stand the new brand up before retiring the old one, and count the number reuse against the three-registration limit."
        ],
        "failureLooksLike": "The switch is planned as a migration, the request is refused at the registry mid-move, and there is no transfer to fall back on while the new registration stalls."
      }
    },
    {
      "id": "BRD-091",
      "slug": "brd-091",
      "title": "A complete physical business address is required",
      "statement": "Street, city, state or region, postal code and a two-letter country must all be populated.",
      "rationale": "The address is matched component by component against the registration record, so a missing component is not a partial match — it is a failed one. Half-filled addresses come from integrations that map a single free-text address string into the first field and leave the rest empty.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street + city + state + postal_code + country",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "TCR",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate all five components separately, matching the address on your IRS letter or business registration. Done when each field holds its own value rather than one field holding the whole address.",
      "example": "street: 1240 Mission St, Suite 400 · city: San Francisco · state: CA · postal_code: 94103 · country: US",
      "pitfalls": [
        "Where the registered address carries a suite number, omitting it is a component mismatch even though the post would arrive."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-092",
      "slug": "brd-092",
      "title": "US and Canadian addresses use the two-letter state code",
      "statement": "For a US or Canadian address, the state or province must be the two-letter abbreviation.",
      "rationale": "The address match is a string comparison against a record that holds the abbreviation, so \"California\" and \"CA\" are simply different values and the address fails to validate. It is the most common single-component address rejection, and the least obvious, because the address is otherwise perfectly correct.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.state",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "TCR",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the full name with the two-letter USPS or Canada Post abbreviation, in upper case. Done when the field holds exactly two letters.",
      "example": "state: CA — not California, not Calif.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-093",
      "slug": "brd-093",
      "title": "The address must match the registration record component by component",
      "statement": "The registered address must match the address on the IRS or national registration record exactly, including abbreviation style.",
      "rationale": "The address is compared as a set of strings rather than resolved as a place, so \"1240 Mission Street\" against a record holding \"1240 Mission St\" is a failed component and therefore a failed address. Every business writes its own address the way it looks best on a letterhead, and the registration record was typed once, years ago, by somebody else.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street … brand.postal_code",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx",
        "TCR",
        "HighLevel"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30795",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Copy the address from the IRS letter or the registry extract rather than from the website, keeping its abbreviations exactly — St or Street, Ste or Suite, whichever the record uses. Done when every component is character-identical to the record.",
      "notes": "Absorbs BRD-094, which states how the comparison is made rather than a separate requirement. We hold no registration record to compare against, so this is the one check the user has to run by eye: put the letter next to the form and read the two addresses line by line, including the abbreviations, before submitting.",
      "catalogIds": [
        "BRD-094"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is every component of the address character-identical to the address on the IRS letter or registry extract, abbreviations included?",
        "howToCheck": [
          "Put the letter next to the form and read the two addresses line by line — this is the one check that has to be done by eye.",
          "Match the record's abbreviation style exactly: St or Street, Ste or Suite, whichever it uses.",
          "Copy from the letter, never from the website — the website is written to look good on a letterhead."
        ],
        "failureLooksLike": "\"1240 Mission Street\" against a record holding \"1240 Mission St\". One failed component is a failed address; the comparison never resolves it as a place."
      }
    },
    {
      "id": "BRD-095",
      "slug": "brd-095",
      "title": "The address must validate as deliverable",
      "statement": "House number, street, locality and postal code must all resolve to a deliverable address at the postal authority.",
      "rationale": "Providers run the address through USPS or Canada Post and reject each component separately, so the rejection arrives as \"Invalid Locality\" or \"Invalid House Number\" — precise, and useless to someone who can see the mail arriving at that address every day. The usual cause is a suite number in the street line where the validator wants it as a secondary component, or a locality that is the neighbourhood rather than the post town.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street … brand.postal_code",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "Sinch",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30754",
          "remediable": true
        },
        {
          "provider": "Sinch",
          "code": "CR2002",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Run the address through the USPS ZIP Code lookup before submitting and use the standardised form it returns, including its abbreviations and its ZIP+4. Done when the postal authority returns exactly the address you are about to submit.",
      "notes": "We do not query the postal authority. The user has to do this themselves, and the USPS lookup is the fastest route: it returns the canonical form, which is also the form BRD-093 wants compared against the registration record.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the postal authority return exactly the address you are about to submit?",
        "howToCheck": [
          "Run the address through the USPS ZIP Code lookup (or Canada Post) and use the standardised form it returns, including its abbreviations and ZIP+4.",
          "Watch two specific components: a suite number sitting in the street line when the validator wants it separately, and a locality that is the neighbourhood rather than the post town."
        ],
        "failureLooksLike": "The rejection reads \"Invalid Locality\" or \"Invalid House Number\" — precise, and useless to someone who watches the mail arrive at that address every day."
      }
    },
    {
      "id": "BRD-096",
      "slug": "brd-096",
      "title": "Brand address must be a physical address, not a PO box",
      "statement": "The registered business address must be a street address; PO boxes are rejected.",
      "rationale": "The registered address is meant to locate a real operating business, and a PO box locates a mailbox. It is a cheap, mechanical check that providers apply consistently, so it rejects reliably.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "Sinch",
        "Infobip",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Provide the physical street address where the business actually operates, including suite or unit number. If you work from home and prefer not to publish it, use a registered agent or coworking address that receives mail — but it must be a street address, not a PO box or mailbox service.",
      "example": "123 Market St, San Francisco, CA 94103",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-097",
      "slug": "brd-097",
      "title": "Include the suite or unit number the registry holds",
      "statement": "Where the registration record carries a suite, unit or apartment number, the brand address must carry it too.",
      "rationale": "A missing secondary component reads as a partial match, and partial matches lower the trust score rather than failing outright — so nothing is rejected and the brand quietly lands in a worse carrier class. Businesses omit it because the post arrives without it, which is true and irrelevant to a string comparison.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street (secondary line)",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Quo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the suite or unit exactly as the registration record writes it, in the same field as the street where the form has only one address line. Done when the brand address carries every component the record does.",
      "notes": "What the registry holds is not visible to us. The user has to check their own registration extract or IRS letter for a secondary component and reproduce it — including the abbreviation, since \"Suite 400\" and \"Ste 400\" are different strings to the comparison.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the registration record carry a suite, unit or apartment number — and if so, is it on the brand address too?",
        "howToCheck": [
          "Look for a secondary component on your registration extract or IRS letter. Post arriving without it proves nothing; the comparison is against the record, not the postman.",
          "Reproduce the abbreviation as the record writes it. \"Suite 400\" and \"Ste 400\" are different strings."
        ],
        "failureLooksLike": "Nothing is rejected. The partial match lowers the trust score, and the brand quietly lands in a worse carrier class with no finding to point at."
      }
    },
    {
      "id": "BRD-098",
      "slug": "brd-098",
      "title": "Register the company address, not the branch you work from",
      "statement": "A branch or local-office address must not be submitted where the official registered company address differs.",
      "rationale": "Whoever fills in the form gives the address they sit at, which for any business with more than one site is not the registered one. It is not rejected outright — it drags the trust score down, which is worse, because the brand verifies and then underperforms with no finding to point at.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the address on the company registration — the registered office or principal place of business — even where nobody sits there. Branch addresses belong on the website and in the campaign content, not on the brand record.",
      "notes": "Nothing on the form distinguishes a branch from a head office. The user has to establish which address their registration record holds and use that one, which usually means asking whoever files the company's returns rather than whoever is filling in this form.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the address on this brand the registered company address, rather than the office whoever filled in the form sits in?",
        "howToCheck": [
          "Ask whoever files the company's returns which address the registration record holds — the registered office or principal place of business.",
          "Use that one even if nobody sits there. Branch addresses belong on the website and in campaign content, not on the brand record."
        ],
        "failureLooksLike": "A regional office address is submitted because that is where the person filling in the form works. The brand verifies and then underperforms, with the score drag and no finding to explain it."
      }
    },
    {
      "id": "BRD-099",
      "slug": "brd-099",
      "title": "The address is checked against spam and scam complaint data",
      "statement": "The registration address must not appear in known spam or scam complaint data.",
      "rationale": "Fraudulent registrations cluster at a small number of addresses — virtual offices, mail forwarders, addresses reused across dozens of shell brands — so the address itself is screened against complaint intelligence. A legitimate business at a shared or virtual office can inherit that history without ever knowing about it, and the rejection gives no reason a business could act on.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Sinch",
          "code": "CR2023",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where a rejection points at the address, move the registration to the physical premises the business actually occupies rather than to a registered-agent or virtual-office address. Where the business genuinely has no other address, expect to appeal with evidence of occupancy — a lease or a utility bill.",
      "notes": "Threat-intelligence data we do not hold. The user cannot check this in advance; what they can do is avoid the pattern that triggers it, which is registering at a mail-forwarding or virtual-office address shared with unrelated brands.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is this the physical premises the business occupies, rather than a virtual office, mail forwarder or registered-agent address?",
        "howToCheck": [
          "Establish what kind of address this is. Fraudulent registrations cluster at addresses shared across dozens of unrelated shell brands, and the screening is against that history rather than against you.",
          "Where the business genuinely has no other address, have evidence of occupancy ready for an appeal — a lease or a utility bill."
        ],
        "failureLooksLike": "A legitimate business at a shared virtual office inherits a complaint history it never knew about, and the rejection gives no reason it could act on."
      }
    },
    {
      "id": "BRD-101",
      "slug": "brd-101",
      "title": "Express PIN delivery needs a real street address",
      "statement": "A PO box, mailbox service or general-delivery address cannot be used when express PIN delivery is requested for political vetting.",
      "rationale": "Political vetting confirms identity by couriering a PIN to the registered address, and couriers do not deliver to a PO box or a mailbox service — so the vet is bought, the PIN is dispatched, and it comes back undelivered. The fee is spent either way, and the campaign schedule is the thing that actually suffers, because political programmes are usually dated.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.street",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Give a street address where a courier can hand the envelope to a person during business hours — the campaign office, not the treasurer's mailbox. Done when the address has a house number and a street and no box number.",
      "example": "street: 1240 Mission St, Suite 400 — not \"PMB 118, 1240 Mission St\" and not a UPS Store box",
      "pitfalls": [
        "A mailbox-service address looks like a street address by design: \"1240 Mission St #118\" is a street address, and \"PMB 118\" at the same building is not."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-102",
      "slug": "brd-102",
      "title": "A brand support phone number is required",
      "statement": "Every brand must supply a support phone number.",
      "rationale": "The support number is what a carrier investigating a complaint, and a consumer answering HELP, are given as the route to a person. A brand with no number reachable behind it is exactly the shape of the traffic 10DLC exists to filter, so its absence weighs more than the effort of supplying it suggests.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.phone",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Supply a number that reaches your business during business hours, and use the same one in your HELP auto-reply. Done when the field is populated and the number answers.",
      "example": "phone: +14155550134",
      "pitfalls": [
        "A number that rings to an unmonitored voicemail satisfies the field and fails the check a reviewer actually runs, which is to call it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-103",
      "slug": "brd-103",
      "title": "The support phone must be in E.164",
      "statement": "The brand support phone must be submitted in E.164: a leading plus, the country code, and digits with no separators.",
      "rationale": "E.164 is the only form the registry parses unambiguously — a number with dashes or brackets is either rejected outright or silently truncated, and a truncated support number is worse than a missing one because nothing flags it. Forms that display a friendly format and submit what was displayed are the usual cause.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.phone",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip the spaces, dashes, dots and brackets, and prefix the country code with a plus. A US number becomes +1 followed by ten digits. Done when the value matches +[country code][digits] with nothing else in it.",
      "example": "phone: +14155550134 — not 415-555-0134 and not (415) 555-0134",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-104",
      "slug": "brd-104",
      "title": "The support phone must reach the business",
      "statement": "The brand support number must route to the business and be answerable during business hours.",
      "rationale": "Reviewers call it. That is the whole check: a number that rings out, reaches a personal voicemail, or lands on a disconnected line tells them the business either does not exist or cannot be contacted, and both readings end the same way. Businesses supply the owner's mobile or a line that was reassigned during an office move, and never learn that the number was the finding.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.phone",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Klaviyo",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the number published on the business website, and make sure it is answered — by a person or by a greeting that names the business — during ordinary business hours. Done when calling it yourself from an unknown number reaches the business by name.",
      "notes": "We cannot dial it. What the user has to do is exactly what the reviewer will: call the number from a phone the business does not recognise, in business hours, and listen to what a stranger hears.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "When you call this number from a phone the business does not recognise, during business hours, does a person or a greeting name the business?",
        "howToCheck": [
          "Do exactly what the reviewer will do: dial it yourself, in business hours, from an unknown number, and listen to what a stranger hears.",
          "Use the number published on the business website, not the owner's mobile — and check it was not reassigned during an office move."
        ],
        "failureLooksLike": "It rings out, or reaches a personal voicemail with no business name. The reviewer reads that as a business that either does not exist or cannot be contacted, and never tells you the number was the finding."
      }
    },
    {
      "id": "BRD-106",
      "slug": "brd-106",
      "title": "The business phone should be findable in a web search",
      "statement": "The registered business phone number should be discoverable through a public web search for the business.",
      "rationale": "Corroboration is how a vetting provider raises a score it cannot otherwise justify: a number that appears on the business's own site, in directories and on listings ties the registration to a business with a history. A brand-new number that appears nowhere is not wrong, it is simply unsupported, and the score reflects that without ever saying so.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.phone",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the same number on the website contact page and in the business's main directory listings before registering, and use that number on the brand. Done when searching the business name and the number together returns the business.",
      "notes": "An open-web search we do not run. The user should do it themselves before submitting — search the business name and see whether this number comes back — because a number that is new everywhere is worth publishing for a week before the registration rather than after it.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does searching the business name and this phone number together return the business?",
        "howToCheck": [
          "Run the search yourself before submitting.",
          "If the number appears nowhere, publish it on the website contact page and the main directory listings and give it a week before registering — corroboration is what lets a vetting provider raise a score it otherwise cannot justify."
        ],
        "failureLooksLike": "A brand-new number is not wrong, it is unsupported. The score reflects that and never says so."
      }
    },
    {
      "id": "BRD-107",
      "slug": "brd-107",
      "title": "The representative's phone number may back only one brand",
      "statement": "An authorised representative's phone number already used on another brand cannot be used again.",
      "rationale": "Reusing a contact number across brands is the signature of one operator registering many businesses, which is the pattern brand-level accountability exists to prevent. Agencies trip it honestly by putting their own account manager on every client brand, and the collision is reported against the newest registration rather than against the practice.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.authorized_rep.phone",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30703",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name a representative who works for the business being registered and use their number. Your own staff belong on the CSP record, which is where a platform's contact details are supposed to live.",
      "notes": "The collision is against every brand in the registry, which we cannot see. What the user has to do is use a different, genuine representative per brand — and if an agency has already spread one number across several brands, expect the next registration to be the one that is refused rather than the earlier ones.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the representative on this brand an employee of the business being registered, with a number used on no other brand?",
        "howToCheck": [
          "Check who is named. An agency account manager on every client brand is the usual cause, and it is reported against the newest registration rather than against the practice.",
          "Your own staff belong on the CSP record, which is where a platform's contact details are meant to live."
        ],
        "failureLooksLike": "One number spread across six client brands. The seventh registration is refused for a collision the earlier six caused."
      }
    },
    {
      "id": "BRD-108",
      "slug": "brd-108",
      "title": "The brand support email must be well formed",
      "statement": "The brand support email must be a syntactically valid address.",
      "rationale": "A malformed address fails at the API boundary, and where it does not, it silently swallows the 2FA and verification mail the brand is waiting on — so the registration stalls with no visible cause. Trailing spaces and a missing top-level domain are the two shapes that survive a form's own validation.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Correct the address so it has a local part, an @, and a domain with a dot in it. Done when a test message to it arrives — the verification mail will go to this address.",
      "example": "email: support@acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-109",
      "slug": "brd-109",
      "title": "Brand contact email must not use a free consumer domain",
      "statement": "The brand support/business contact email must be on a domain the business controls, not a free consumer mail provider.",
      "rationale": "A business-domain email is weak evidence that the registrant controls the business they are registering. Free consumer addresses are the pattern fraudulent registrations use, so several providers reject them outright rather than weighting them.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "TCR",
        "Sinch",
        "Bandwidth",
        "Telnyx"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Use an email address on your business domain (e.g. support@yourcompany.com). Free consumer addresses are rejected for registered entities.",
      "example": "support@acmecoffee.com",
      "notes": "Sole proprietors are carved out — they legitimately have no business domain. Severity varies by authority (MEDIUM at carriers, BLOCKING at AWS/TCR/Sinch); strictest kept.",
      "catalogIds": [
        "BRD-110"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-111",
      "slug": "brd-111",
      "title": "The contact email domain must be provably the brand's",
      "statement": "The brand contact email must be on a domain the business demonstrably owns — shown on its website, matching WHOIS, or verified by DNS.",
      "rationale": "Authentication+ sends a PIN to this mailbox and treats whoever answers it as the business, so domain ownership is the thing being verified rather than a formality about tidy addresses. A domain that appears nowhere on the brand's own site cannot be tied back to it, and the verification is refused rather than delayed.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_contact_email domain",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21736",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a mailbox on the same domain as the registered website, and make sure that domain appears as a contact address somewhere on the site. Where the business uses a different mail domain, publish it on the site's contact page before submitting. Done when the domain in the email can be found on the brand's own website.",
      "example": "business_contact_email: jane.doe@acmecoffee.com · website: https://acmecoffee.com",
      "notes": "Absorbs BRD-118, which is the same ownership requirement stated for the Auth+ flow. Recommended universally and mandatory for public companies. The crawl can tell us whether the domain appears on the site; WHOIS and DNS TXT verification are external, so where the site does not show the address the user has to establish ownership another way — usually by publishing it on the contact page, which is also the cheapest fix.",
      "catalogIds": [
        "BRD-118"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Can the email domain on this brand be found on the brand's own website?",
        "howToCheck": [
          "Open the registered website and look for an address on this domain — the contact page is where a reviewer looks.",
          "Where the business uses a different mail domain from its website, publish it on the contact page before submitting. That is the cheapest way to establish ownership.",
          "Authentication+ sends a PIN to this mailbox and treats whoever answers it as the business, so the domain is the thing being verified."
        ],
        "failureLooksLike": "A mail domain that appears nowhere on the site cannot be tied back to the business, and the verification is refused rather than delayed."
      }
    },
    {
      "id": "BRD-112",
      "slug": "brd-112",
      "title": "Brand contact email must not be a role or distribution address",
      "statement": "The brand business-contact email must be an individual mailbox, not a role or group address such as info@ or sales@.",
      "rationale": "The contact address receives the Authentication+ verification PIN, and a shared inbox either loses it or is not monitored inside the short expiry window. Several providers reject role addresses outright for that reason, which surprises brands who deliberately used a \"professional\" address.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a named individual mailbox on the business domain that someone actually monitors — the verification PIN expires quickly and cannot be resent indefinitely.",
      "example": "jane.doe@acmecoffee.com rather than info@acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-114",
      "slug": "brd-114",
      "title": "Disposable mailboxes are refused outright",
      "statement": "The brand contact email must not be on a disposable or temporary mail domain.",
      "rationale": "A throwaway address means nobody can be reached at the business tomorrow, which defeats the point of collecting a contact at all — and every verification message, PIN and carrier enquiry goes to it. It is refused rather than scored down, and it reaches the field honestly during testing, when somebody wanted a working address without using their own.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30753",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a monitored mailbox on the business domain. If you are testing, register a mock brand (BRD-204) rather than a real one with a temporary address. Done when the address will still receive mail in six months.",
      "example": "email: support@acmecoffee.com — not a mailinator.com or 10minutemail.com address",
      "notes": "Absorbs BRD-079, which states the same requirement for sole-proprietor brands. The check carries the well-known disposable providers; the full list is maintained externally and changes constantly, so a domain passing here is not proof it is off the provider's list — the user still has to use a mailbox the business genuinely keeps.",
      "catalogIds": [
        "BRD-079"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Will this mailbox still receive mail in six months, and is it monitored by someone at the business?",
        "howToCheck": [
          "Confirm the domain is the business's own, not a temporary or disposable mail service. The provider's blocklist is maintained externally and changes constantly, so passing our check is not proof it is off theirs.",
          "Testing rather than registering? Use a mock brand instead of a real one with a throwaway address."
        ],
        "failureLooksLike": "A temporary address entered during testing reaches production. Every verification message, PIN and carrier enquiry goes to a mailbox that no longer exists, and the brand is refused rather than scored down."
      }
    },
    {
      "id": "BRD-115",
      "slug": "brd-115",
      "title": "The email domain must resolve",
      "statement": "The brand contact email domain must resolve with an MX or A record.",
      "rationale": "A domain with no mail records cannot receive the verification message, so the registration stalls waiting for a reply that could never arrive. It happens to businesses that registered a domain for the website and never configured mail on it, and to anyone who mistypes a domain into an address that otherwise looks perfect.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email domain",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send a test message to the address from an outside account and confirm it arrives. If it bounces, the domain has no mail service — use a domain that does, or configure mail on this one before registering.",
      "notes": "A DNS lookup we do not perform. The user's equivalent is the test message: sending one from a personal account settles both the MX record and the mailbox in a single step, which is worth doing before the registration rather than after it stalls.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Did a test message sent from an outside account actually land in this mailbox?",
        "howToCheck": [
          "Send one message from a personal account and confirm it arrives. That settles the MX record and the mailbox in a single step.",
          "If it bounces, the domain has no mail service — use a domain that does, or configure mail on this one before registering."
        ],
        "failureLooksLike": "A domain registered for the website with mail never configured. The registration stalls waiting for a reply to a message that could never have arrived."
      }
    },
    {
      "id": "BRD-117",
      "slug": "brd-117",
      "title": "A public company must supply a business contact email",
      "statement": "A PUBLIC_PROFIT brand must carry a business contact email — it is mandatory and it triggers the Authentication+ 2FA.",
      "rationale": "For a public company this field is not a contact detail, it is the second factor: the PIN goes to it and the brand cannot reach VERIFIED without one. Omitting it does not produce a missing-field error people recognise — the brand sits in an authentication-required state, which reads as a queue rather than as a blank box.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_contact_email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30994",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Supply a named individual's mailbox on the company domain, monitored by someone who will act on a verification mail within days. Done when the field is populated and that person knows a PIN is coming.",
      "example": "business_contact_email: jane.doe@acmecoffee.com — an individual mailbox on the company domain",
      "pitfalls": [
        "The PIN expires (BRD-227), so an address that is technically valid but monitored monthly fails the verification just as surely as an empty field."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-119",
      "slug": "brd-119",
      "title": "Confirm the mailbox accepts outside mail before triggering 2FA",
      "statement": "The brand contact mailbox must be able to receive external mail before Authentication+ 2FA is triggered.",
      "rationale": "Corporate mail filters reject or quarantine unfamiliar senders, and a verification mail that is silently dropped looks identical to one that was never sent — the brand waits in an authentication-required state and nobody knows why. Larger companies, which are exactly the ones registering as public, have the strictest filters.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_contact_email deliverability",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21738",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Send a test message from an outside domain to this mailbox and confirm it lands in the inbox rather than in quarantine. Where IT filters unknown senders, have the provider's sending domain allow-listed before the vet is ordered.",
      "notes": "Deliverability is only observable after the mail is sent. What the user has to do is test it first — one message from a personal account — and, in a filtered environment, get the verification sender allow-listed before ordering the vet, because the 30-day window (BRD-226) runs whether or not the mail arrived.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has a test message from an outside domain reached this mailbox's inbox, rather than its quarantine?",
        "howToCheck": [
          "Send the test from a personal account and have the contact confirm it landed in the inbox — not in a junk folder or a quarantine digest.",
          "In a filtered corporate environment, get the provider's sending domain allow-listed by IT before the vet is ordered.",
          "The 30-day vet window runs whether or not the mail arrived, so do this first."
        ],
        "failureLooksLike": "A silently quarantined verification mail is indistinguishable from one that was never sent. The brand waits in an authentication-required state and nobody knows why."
      }
    },
    {
      "id": "BRD-120",
      "slug": "brd-120",
      "title": "Changing a public brand's contact email resets its identity",
      "statement": "Any change to a PUBLIC_PROFIT brand's business contact email resets identity verification and requires the 2FA to be completed again.",
      "rationale": "The mailbox is the second factor, so replacing it replaces the evidence the verification rests on — the brand drops out of VERIFIED and the Authentication+ state goes with it. Teams make this change for entirely routine reasons, usually when the named person leaves, and discover the brand is unverified only when the next campaign is refused.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_contact_email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30994",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Plan the change: the new contact must be available to answer a PIN within days of the edit, and campaign registration is blocked until they do. Where the change is only a departing employee, prefer a role-independent mailbox on the same domain that a person actually monitors.",
      "notes": "The reset happens at the registry once the change is submitted. What the user has to do is sequence it — make the change when the new contact is at their desk, not during their holiday — because the brand is unverified for the whole gap, and every campaign under it is refused meanwhile.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Will the new contact be at their desk and able to answer a PIN within days of this change?",
        "howToCheck": [
          "Sequence the edit around the person: make it when the new contact is available, not the day before their holiday.",
          "Expect the brand to leave VERIFIED and campaign registration to be blocked for the whole gap.",
          "Where the change is only a departing employee, prefer a role mailbox on the same domain that a named person actually monitors."
        ],
        "failureLooksLike": "The contact is updated routinely when someone leaves. The brand is silently unverified, and the team finds out when the next campaign is refused."
      }
    },
    {
      "id": "BRD-121",
      "slug": "brd-121",
      "title": "The support contact must be the end customer's, not the agency's",
      "statement": "The brand contact email must reach the business being registered, not the ISV, agency or reseller that filed the registration.",
      "rationale": "The contact address is where a carrier investigation and a consumer complaint land, so an agency address puts a middleman between the regulator and the business that owns the consent. It also drives a specific failure the agency never sees: the verification mail goes to the agency, the customer never learns their brand needs action, and the registration stalls while both sides wait for the other.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email vs the registering tenant",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30881",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30894",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the end customer's own support mailbox in the brand email field — the one printed on their website. Your own address belongs on the CSP or reseller record, where it will still receive the API notifications. Done when the brand email is on the customer's domain.",
      "example": "brand email: support@acmecoffee.com — not support@northstarplatform.io on a brand registered for Acme Coffee",
      "pitfalls": [
        "A shared mailbox you monitor on the customer's behalf still has to be on their domain. Forwarding is fine; the address is what is compared."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-122",
      "slug": "brd-122",
      "title": "A well-known brand name on a consumer mail domain reads as impersonation",
      "statement": "A widely recognised company name paired with a free or non-corporate email domain must be treated as probable impersonation.",
      "rationale": "Impersonating a bank or a retailer is the highest-value use of a fraudulent brand, and the cheapest tell is that the registrant could not produce an address on the company's own domain. Reviewers act on that tell hard, which means a franchisee or a regional reseller using a personal address gets caught in the same net — the fix for them is a real corporate address, not an explanation.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name + brand.email",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use an email address on the domain the named company actually operates. If you are a franchisee or an authorised reseller, register under your own legal entity name rather than the parent brand's, and use your own domain. Done when the name in the brand field and the domain in the email field belong to the same organisation.",
      "example": "company_name: Acme Coffee Co, LLC · email: support@acmecoffee.com — not acmecoffee@gmail.com",
      "pitfalls": [
        "Adding a location to the name — \"Acme Coffee Portland\" — does not resolve it while the email domain still belongs to nobody in particular."
      ],
      "notes": "Whether a name is a well-known brand, and whether this registrant is authorised to use it, both need data we do not hold. The user has to satisfy themselves that they can evidence the right to register under this name — a franchise agreement or a reseller authorisation — because a reviewer who suspects impersonation will ask for exactly that.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "If you are registering under a name a consumer would recognise, can you produce the document that authorises you to use it?",
        "howToCheck": [
          "Find the franchise agreement, reseller authorisation or letter of appointment that names your entity.",
          "Check the email domain on the brand belongs to the same organisation as the name in the brand field.",
          "No such document: register under your own legal entity name and your own domain instead."
        ],
        "failureLooksLike": "A national brand name paired with a gmail.com address. A reviewer reads that as impersonation and asks for the authorisation — and adding a city to the name does not help while the domain still belongs to nobody in particular."
      }
    },
    {
      "id": "BRD-125",
      "slug": "brd-125",
      "title": "The website URL must carry an explicit scheme",
      "statement": "The brand website must be submitted with an explicit http:// or https:// scheme.",
      "rationale": "A bare domain is not a URL, and the registry does not add a scheme for you — it rejects the field or stores something no crawler can fetch, which reads downstream as a brand with no website. Users type the domain because that is what they say when they tell someone their address.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Prefix the domain with https://. Done when the value starts with https:// and opens in a browser exactly as submitted.",
      "example": "website: https://acmecoffee.com — not acmecoffee.com and not www.acmecoffee.com",
      "pitfalls": [
        "http:// is a scheme and still fails the separate https requirement, so add the s while you are there."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-126",
      "slug": "brd-126",
      "title": "The brand website is the root domain, not a page inside it",
      "statement": "The brand website must be submitted as the root domain rather than a deep link or sub-path.",
      "rationale": "A reviewer given a specific page sees that page and nothing else, and a landing page or a product page rarely carries the business identity, the policy links or the contact details they are looking for. Registrants deep-link with the best intentions — usually to the signup page they are proudest of — and hand the reviewer the one view of the site that proves least.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the site root in the brand website field. The opt-in page still gets cited — it belongs in the campaign message flow, where a deep link is exactly what is wanted. Done when the brand website is a bare domain with no path on it.",
      "example": "website: https://acmecoffee.com — with https://acmecoffee.com/checkout cited in the message flow instead",
      "notes": "The mirror of WEB-090, which asks the message flow to deep-link the opt-in page. The two fields want opposite things and for the same reason: the brand field proves the business exists, the flow field proves the consent surface does.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-132",
      "slug": "brd-132",
      "title": "The website domain must relate recognisably to the brand name",
      "statement": "The registered website's domain must bear a recognisable relationship to the legal name or the DBA.",
      "rationale": "A reviewer confirms a brand exists by opening its website, and a domain with no visible connection to the name gives them nothing to confirm — they cannot tell a rebrand from a borrowed site. Honest causes are everywhere: a company that acquired a better domain, a practice trading under a founder's name on a descriptive URL, a store on a platform subdomain.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.website vs brand.company_name / brand.dba",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register the domain the business actually trades under, and make sure that domain's home page names the business in its title or header. Where the domain genuinely differs from the name, add the trading name to the DBA field so the two can be connected. Done when a reviewer landing on the site can see the registered name on it.",
      "example": "company_name: Acme Coffee Co, LLC · website: https://acmecoffee.com",
      "pitfalls": [
        "A platform subdomain such as a myshopify.com store is the common version of this, and it fails for a second reason as well — it is not the brand's own domain."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-135",
      "slug": "brd-135",
      "title": "A website may back only one brand",
      "statement": "A website URL already registered against another brand cannot be registered again.",
      "rationale": "The site is treated as an identity key, so two brands claiming the same one is read as one business registered twice — which is the thing per-brand accountability exists to prevent. Franchise groups and multi-location businesses hit it legitimately: every location wants its own brand, and they all share the corporate site.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30703",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give each brand its own web presence — a location page on its own subdomain, or the franchisee's own site — rather than pointing several brands at one corporate root. Where the same legal entity is behind both, register one brand and run several campaigns under it instead.",
      "notes": "The collision is registry-wide and invisible to us. What the user has to establish is whether anyone else — a franchisor, a former agency, another location — has already registered a brand against this URL, because the second registration is the one that is refused.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has any other brand — a franchisor, another location, a former agency — already been registered against this website?",
        "howToCheck": [
          "Ask the franchisor or head office whether a brand already exists on the corporate URL.",
          "Where several locations need their own brands, give each its own web presence: a location page on its own subdomain, or the franchisee's own site.",
          "Where the same legal entity is behind both, register one brand and run several campaigns under it."
        ],
        "failureLooksLike": "Six locations point at one corporate root. The first registration works and every later one is refused, against a record the franchisee cannot see."
      }
    },
    {
      "id": "BRD-143",
      "slug": "brd-143",
      "title": "A brand with no website must attach its policy document",
      "statement": "Where the brand registers no website, a compliant policy document must be attached to the registration or hosted at a public URL.",
      "rationale": "Every consent and disclosure requirement assumes a page a reviewer can open. A business with no website is not disqualified — plenty of legitimate senders have none — but it has to supply the same content some other way, and nothing on the form says so. The registration is refused for missing policies that the business would happily have provided.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "policy attachment",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Alive5",
        "BeeTexting"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the privacy policy and the SMS terms at a public URL — a hosted document, a Google Doc set to public, or a one-page site — and cite them on the registration. Done when both documents open in a browser with no login and are reachable from the registration.",
      "example": "policy_document_url: https://cdn.acmecoffee.com/compliance/sms-privacy-policy.pdf — where no website is registered",
      "pitfalls": [
        "A document behind a login, or one that downloads rather than displays, cannot be reviewed. The test is whether a stranger can read it in a browser."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-144",
      "slug": "brd-144",
      "title": "The registered vertical must match the business on the website",
      "statement": "The declared industry vertical must match the business the registered website actually describes.",
      "rationale": "The vertical is one of the inputs to use-case eligibility and to the trust score, and a reviewer checks it in the cheapest possible way: they open the site and see whether it looks like that kind of business. A mismatch reads as a brand describing itself as something safer than it is, which is a much less charitable reading than the truth — usually that the closest of twenty-three options was chosen quickly.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical vs website content",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the vertical a stranger would choose after thirty seconds on your home page. Where nothing fits well, choose the one covering most of the revenue rather than the most flattering one. Done when the vertical and the site's own description of the business agree.",
      "example": "vertical: RETAIL for acmecoffee.com, which sells coffee — not HOSPITALITY because there is a café",
      "notes": "BRD-150 checks the value is one of the 23; BRD-151 asks whether it came from the right vocabulary. This rule is the third question: whether it is TRUE of the business, judged against the site.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-145",
      "slug": "brd-145",
      "title": "A display name is required, and so is the DBA field",
      "statement": "The display name must be populated within its length bound, and the DBA field must be populated even when it is identical to the legal name.",
      "rationale": "The display name is the string a consumer sees attributed to the messages, so an empty one leaves the brand identified only by a legal entity nobody recognises. The DBA field trips people separately: it reads as optional when the business does not trade under a different name, and Telnyx requires it populated regardless — repeating the legal name there is the expected answer, not a workaround.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.display_name + brand.dba",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate the display name with the name customers know you by, and copy it into the DBA field if you do not trade under anything else. Done when both fields hold a value, even if it is the same value twice.",
      "example": "display_name: Acme Coffee · dba: Acme Coffee — repeated deliberately, not left blank",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-146",
      "slug": "brd-146",
      "title": "The DBA must be consistent with the legal name and every other field",
      "statement": "The DBA must be recognisably the same business as the legal name, the website, and the campaign — not an unrelated third name.",
      "rationale": "Reviewers use the DBA as the bridge between a legal name nobody recognises and a website that carries a brand. When the DBA is a third, unrelated name, that bridge collapses and the registration reads as three businesses stapled together — which is what an unauthorised reseller registration actually looks like. Multi-brand retailers reach this honestly by putting the group name in one field and a shopfront in another.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.dba vs brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Bandwidth",
        "HighLevel"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30918",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the DBA to the name customers actually see from this brand — the one on the website header and in the message sender line. If the business genuinely trades under several names, register a separate brand per name rather than listing one and messaging under another. Done when the DBA, the website title and the name in the samples are the same business.",
      "example": "company_name: Acme Coffee Co, LLC · dba: Acme Coffee · website: acmecoffee.com — one business, three consistent renderings",
      "notes": "Bandwidth grades this MEDIUM and AWS HIGH; the strictest is kept per the strict-superset model.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-147",
      "slug": "brd-147",
      "title": "A DBA used publicly must be registered on the brand",
      "statement": "Any trading name the business uses publicly — on its site or in its messages — must be declared in the DBA field rather than substituted silently.",
      "rationale": "The consumer's only handle on who is texting them is the name in the message, so a name that appears there and nowhere in the registration breaks the identification the whole framework rests on. It is rarely deliberate: the messaging programme gets its own friendly name, the registration was filled in months earlier, and nobody connects the two.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.dba vs website and message content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30918",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the public trading name to the brand's DBA field. Where more than one name is used publicly, register the one that appears in the messages — that is the one a recipient will try to match. Done when the name a customer sees in a text can be found on the brand record.",
      "example": "dba: Acme Coffee — where the site header, the samples and the HELP reply all say \"Acme Coffee\" but the legal name is Acme Coffee Co, LLC",
      "notes": "Whether a DBA is REGISTERED with the state is EXTERNAL_DATA and out of reach; what we can see is whether a publicly used name was declared to us. The user has to confirm separately that any DBA they rely on is properly filed where their state requires it, because a reviewer who looks it up and finds nothing treats the name as invented.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is every trading name you use publicly — in messages, on the site — registered where your state requires a DBA to be filed?",
        "howToCheck": [
          "List the names a customer could see: the sender name in your texts, the name on the website header, the name on receipts.",
          "For each, confirm it is either the legal name or a DBA filed with the state or county that requires it.",
          "Put the name that appears in the messages in the brand's DBA field — that is the one a recipient will try to match."
        ],
        "failureLooksLike": "The messaging programme was given a friendly name of its own. A reviewer looks the name up, finds no filing behind it, and treats it as invented."
      }
    },
    {
      "id": "BRD-149",
      "slug": "brd-149",
      "title": "The opt-in evidence must be on the brand's own domain",
      "statement": "The website domain shown in the opt-in evidence must match the brand's registered domain.",
      "rationale": "Opt-in evidence naming a different company than the brand is the single clearest sign that the wrong entity was registered — the consent belongs to whoever is named on the page, and that is not the brand being registered. It is also what an ISV registration looks like from the outside, which is why reviewers treat it as decisive rather than as a filing error.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "evidence domain vs brand.website",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30927",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach evidence captured on the brand's own site. If the opt-in genuinely happens on another company's domain, that company is the brand — register them instead. Done when the domain visible in the evidence and the registered website are the same business.",
      "example": "evidence: https://cdn.acmecoffee.com/compliance/checkout-optin.png · website: https://acmecoffee.com — the same registrable domain",
      "pitfalls": [
        "A CDN or asset subdomain of the brand's own domain is fine; a hosting service on somebody else's domain is not, because the reviewer cannot tell whose page it was."
      ],
      "notes": "Compares the registrable domain of each hosted artifact against the brand website. What it cannot see is the domain rendered INSIDE a screenshot — an artifact hosted on the brand's CDN can still show another company's checkout — so a PASS here is a necessary condition rather than a sufficient one, and the vision rules judge the picture itself.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-150",
      "slug": "brd-150",
      "title": "The vertical must be one of the 23 registry values",
      "statement": "The industry vertical must be one of the 23 TCR values, and is required for every entity type except sole proprietor.",
      "rationale": "The vertical drives use-case eligibility and part of the trust score, and the registry rejects a value outside its own vocabulary rather than mapping it to the nearest match. The trap is that the neighbouring vocabularies are plausible: Twilio publishes a longer business-industry list, so NON_PROFIT and BANKING look like valid verticals and are not — TCR spells the first NGO and has no entry for the second.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Telnyx",
        "Twilio"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Choose the vertical from the TCR list of 23. A charity is NGO, not NON_PROFIT; a bank is FINANCIAL, not BANKING. Done when the submitted string is character-identical to one of the 23 values.",
      "example": "vertical: NGO — the TCR value for a charity, where Twilio's list would say NON_PROFIT",
      "pitfalls": [
        "A form built against Twilio's business-industry enum will offer values TCR rejects, and the field will look correctly populated right up to the rejection."
      ],
      "notes": "The mechanical half of the vertical question. BRD-151 judges whether the chosen vertical is PLAUSIBLE for the business; this rule only asks whether the value exists. A vertical can pass here and fail there.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-151",
      "slug": "brd-151",
      "title": "Vertical must come from the correct provider-specific list",
      "statement": "The industry vertical must be chosen from the list the target platform uses, which is not always the TCR vertical list.",
      "rationale": "Twilio's business-industry enum and the TCR vertical list are different vocabularies with different values, so populating one from the other produces values the platform rejects or silently coerces. It is invisible in a form that renders whichever list it was built against.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the vertical from the list your submission target actually publishes, and re-check it if you switch providers.",
      "example": "TCR uses RETAIL; Twilio's business-industry list has separate values such as BANKING and JEWELRY.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-153",
      "slug": "brd-153",
      "title": "brandRelationship must be one of the five account tiers",
      "statement": "Outside the sole-proprietor tier, brandRelationship must be one of BASIC_ACCOUNT, SMALL_ACCOUNT, MEDIUM_ACCOUNT, LARGE_ACCOUNT or KEY_ACCOUNT.",
      "rationale": "This field describes the CSP's commercial relationship with the brand, not the brand's size, and it is mandatory — so a submission that leaves it out or invents a value is rejected on a field the customer has never heard of and the integrator guessed at. Nothing downstream depends on which tier is chosen, which is exactly why it gets filled in carelessly.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.brand_relationship",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Set brandRelationship to the tier that describes your commercial relationship with this customer — BASIC_ACCOUNT is the honest answer for most self-serve registrations. Done when the value is one of the five, in upper case with the underscore.",
      "example": "brand_relationship: BASIC_ACCOUNT",
      "notes": "Checked only where a value is present: a registration that has not reached this field yet is undecided rather than wrong. The field is genuinely required at submission, so an empty one will be rejected by the API even though this rule stays quiet about it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-154",
      "slug": "brd-154",
      "title": "The account tier cannot be changed more than once a quarter",
      "statement": "brandRelationship must not be changed more than once in any three-month period.",
      "rationale": "The tier feeds commercial reporting rather than compliance, so the registry rate-limits it instead of validating it — and the rejection arrives as a bare error code on an edit that seemed trivial. It bites during account reviews, when someone tidies up several brands at once and one of them was already touched last month.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.brand_relationship",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "528",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Leave the tier as it is until three months have passed since the last change. Nothing about the brand's standing depends on it, so there is no cost to waiting — and no route to an exception if you do not.",
      "notes": "The cooldown is enforced at the registry against a change history we only see when the caller supplies it. The user has to know when the tier was last edited; where that is unknown, the safe assumption is that it was recent, because this field is usually only touched during exactly the kind of review that would touch it twice.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Do you know when this brand's account tier was last changed, and was it more than three months ago?",
        "howToCheck": [
          "Check the brand's edit history for a previous change to the tier.",
          "Where the date is unknown, assume it was recent — this field is usually only touched during exactly the kind of account review that would touch it twice."
        ],
        "failureLooksLike": "Someone tidies up several brands at once and one was already edited last month. The rejection is a bare error code on an edit that seemed trivial, and there is no exception route."
      }
    },
    {
      "id": "BRD-155",
      "slug": "brd-155",
      "title": "Regions of operation come from a five-value enum",
      "statement": "business_regions_of_operation must be drawn from AFRICA, ASIA, EUROPE, LATIN_AMERICA and USA_AND_CANADA.",
      "rationale": "The list is five continent-sized buckets, and every natural way to answer the question — a country, a state, \"North America\", \"Global\" — is outside it. The submission is rejected on a field that reads like free text, and the value that was typed is usually more precise than the one that is wanted.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_regions_of_operation",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the buckets the business actually operates in from the five, and pass them as an array. A US-only business sends USA_AND_CANADA and nothing else. Done when every value is one of the five, in upper case.",
      "example": "business_regions_of_operation: [\"USA_AND_CANADA\"]",
      "notes": "A Twilio Trust Hub field that TCR does not carry, so it is checked only where it is populated. Not tagged with `providers`: the tag budget is reserved for genuine baseline divergences, and an unpopulated field costs nothing here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-156",
      "slug": "brd-156",
      "title": "The representative needs an enum job position and their real title",
      "statement": "job_position must come from Director, GM, VP, CEO, CFO, General Counsel or Other, and the representative's exact business title must be supplied alongside it.",
      "rationale": "Two fields describe the same person and only one of them is constrained: the enum is what the API validates, and the free-text title is what a reviewer compares against the company's own website. Submitting the real title in the enum field fails validation, and submitting the enum value in the title field gives the reviewer nothing to corroborate.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.authorized_rep.job_position + .title",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30701",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Choose the closest enum value — Other is a legitimate answer for most titles — and put the person's actual title in the title field, spelled as it appears on the company site or their signature block. Done when the enum validates and the title is a real one.",
      "example": "job_position: CEO · title: Chief Executive Officer — or job_position: Other · title: Head of Customer Operations",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-157",
      "slug": "brd-157",
      "title": "Say whether you are the business or a reseller registering for it",
      "statement": "The business-identity and reseller flags must say correctly whether the registering party is the brand itself or an ISV, reseller or partner.",
      "rationale": "This flag is how the platform knows whether to expect the brand's own details or a third party's, and it changes what is treated as a contradiction: an agency address on a direct-customer registration is a finding, while the same address on a declared reseller registration is not. Setting it wrong turns correct data into suspicious data.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.business_identity + brand.is_reseller",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30894",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the flag to direct_customer when the business is registering itself, and to isv_reseller_or_partner when you are registering on someone else's behalf. Declaring the reseller relationship does not weaken the registration — concealing it does. Done when the flag matches who filled the form in.",
      "example": "is_reseller: false · business_identity: direct_customer — for a business registering itself",
      "notes": "Only the internal consistency of the two fields is checkable here. Whether the declared answer is TRUE is BRD-160 and BRD-121, which read it against the campaign and the contact details.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-158",
      "slug": "brd-158",
      "title": "Social profiles corroborate a brand that is otherwise thin",
      "statement": "Social-media profile URLs should be supplied to strengthen brand verification.",
      "rationale": "Vetting scores partly on how much independent evidence exists that the business is real and has a history, and social profiles are the cheapest such evidence to supply — an account with years of posts corroborates a company far better than a website registered last month. Nothing rejects a brand for omitting them, which is why almost everyone does, including the businesses whose score most needed the help.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.social_profiles",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30799",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the business's own profile URLs — the ones with real posting history, not a placeholder account created this week. Two or three established profiles are worth more than five empty ones. Done when the profiles are populated and each opens to the business.",
      "example": "social_profiles: [\"https://www.linkedin.com/company/acme-coffee\", \"https://instagram.com/acmecoffee\"]",
      "notes": "Advisory rather than a rejection, which is why it is LOW and why an empty field PASSES with the advice attached rather than failing: nothing is wrong with a registration that omits it, and failing one would be a false positive on every compliant brand. The rule fails only on URLs that are supplied and unusable. It earns its place because it is one of the very few ways a business can raise a trust score by supplying information rather than by paying for a vet.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-160",
      "slug": "brd-160",
      "title": "Register the message sender, not the agency or platform",
      "statement": "The brand must be the legal entity whose messages are sent — never the agency, ISV, reseller, or software platform acting on its behalf.",
      "rationale": "Consent runs to a specific sender, so the brand on the registration must be the business the consumer agreed to hear from. Registering the agency or platform instead breaks that chain and is the highest-frequency brand rejection in the corpus — 10 separate sources across 11 authorities call it out.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN",
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx",
        "Bandwidth",
        "AWS",
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register the brand under the legal entity that owns the customer relationship and whose name appears in the messages. If you are an agency or platform registering on behalf of a client, the client is the brand — register them, with their EIN and their website, and add yourself as the CSP rather than the brand.",
      "example": "Brand: Acme Coffee Co, LLC (the business texting its own customers) — not \"Northstar Marketing Group\" (the agency running the campaign).",
      "notes": "Highest-frequency brand rejection in the corpus (10 source rows, 11 authorities). A tool that fills the form from whatever website it was pointed at can actively induce this failure.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is the legal entity on this brand the business whose name appears in the messages, rather than the agency or platform that sends them?",
        "howToCheck": [
          "Read one of your sample messages as a recipient would, and note which business it says is texting.",
          "Compare that business against the legal name and EIN on the brand. They must be the same entity.",
          "If you are an agency, ISV or reseller, your client is the brand and you are the CSP — register them, not yourself."
        ],
        "failureLooksLike": "The brand says Northstar Platform, Inc. and the message says \"Acme Coffee: your order is ready\". The consumer consented to hear from Acme, so Acme is the brand and Northstar is not."
      }
    },
    {
      "id": "BRD-163",
      "slug": "brd-163",
      "title": "Name the ISV on a toll-free verification filed for someone else",
      "statement": "Where a toll-free verification is submitted on behalf of an end business, the ISV or reseller name must be disclosed.",
      "rationale": "Toll-free verification asks who is filing as well as who is being verified, because the reviewer needs to know whether an unfamiliar contact in the submission is the business or its vendor. Leaving it out makes the ISV's own details look like inconsistencies in the end business's record, which is a much worse reading than the truth.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "tfv.isv_name",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "TFV 1301",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate the ISV name field with your own company name on every toll-free verification you file for a customer. Done when the submission names both the end business and the party filing it.",
      "example": "isv_name: Northstar Platform, Inc · company_name: Acme Coffee Co, LLC",
      "notes": "Applies on the toll-free verification path rather than to 10DLC brand registration; the condition lives in the check, which passes immediately for a 10DLC submission. Where the path is not declared the rule is silent rather than assuming one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-164",
      "slug": "brd-164",
      "title": "The named contact must be authorised to act for the business",
      "statement": "The brand contact must be an authorised representative of the business, not a generic, third-party or aggregator name.",
      "rationale": "This person is treated as the party who attested to the registration, so the name matters when a carrier asks who authorised the programme. A vendor's account manager or a shared \"Marketing Team\" entry means nobody at the business did — and that is a materially different answer from the one the registration implies.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.authorized_rep",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "AWS",
        "Bandwidth",
        "HighLevel",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30972",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name someone employed by the business with authority to commit it — an owner, an officer, or the manager who owns the messaging programme. Get their agreement before naming them, because they may be contacted about it.",
      "notes": "Nothing on the form shows whether the named person works for the business. What the user has to do is confirm the person is a real employee with authority and knows they have been named — a reviewer who calls the business and finds nobody by that name treats the whole registration as unverified.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the named representative work for the business, have authority to commit it, and know they have been named?",
        "howToCheck": [
          "Confirm the person is a real employee — an owner, an officer, or the manager who owns the messaging programme.",
          "Tell them they have been named and that they may be contacted about it.",
          "Rule out the two substitutes: a vendor's account manager, and a generic entry like \"Marketing Team\"."
        ],
        "failureLooksLike": "A reviewer calls the business and finds nobody by that name. The registration implies somebody at the business attested to it, and nobody did."
      }
    },
    {
      "id": "BRD-165",
      "slug": "brd-165",
      "title": "A toll-free contact needs a full first and last name",
      "statement": "The toll-free registration contact must be an authorised representative's full first and last name.",
      "rationale": "The contact name is checked against the business rather than stored, so a department, a role or a single word gives the reviewer nothing to check and the submission is refused. Teams supply \"Support\" or \"Marketing Team\" precisely because they are trying to avoid naming an individual, which is the opposite of what the field is for.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "tfv.contact_first_name + .contact_last_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Infobip",
          "code": "10000",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name a real person who is authorised to act for the business, first and last name in their own fields. Done when both fields hold a person's name rather than a role or a department.",
      "example": "contact_first_name: Jane · contact_last_name: Doe",
      "notes": "Applies on the toll-free verification path; the check passes immediately for a 10DLC brand submission. Whether the named person is genuinely authorised is BRD-164, which nothing on the form can settle.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-166",
      "slug": "brd-166",
      "title": "The sender must be identified and authenticated before any message goes out",
      "statement": "Sufficient identifying information must be obtained to verify and authenticate the sender's identity before that sender sends any message.",
      "rationale": "This is the know-your-customer duty the whole registration framework implements, and it sits on the aggregator rather than on the brand: nobody may send until somebody has established who they are. It is worth stating explicitly because it explains why so many of the other brand rules are unyielding about evidence — they are how this obligation is actually discharged.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand identity bundle",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Complete brand registration and identity verification before enabling any traffic, and keep the identity evidence on file for as long as the sender is active. Done when no number can send under this brand before its identity status is verified.",
      "notes": "CTIA Messaging Principles §3.2.4 places this duty on the CPaaS or aggregator, not on the brand, so it is not something a registration can satisfy by itself. What the user has to confirm is that their provider actually enforces it — a provider that lets traffic flow before verification is a risk to its customers rather than a convenience, because the enforcement lands later and harder.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does your provider actually block traffic under this brand until its identity status is verified?",
        "howToCheck": [
          "Ask the provider directly, or test it: attempt a send under an unverified brand and see whether it is refused.",
          "Keep the identity evidence on file for as long as the sender is active — this duty sits on the aggregator, and the evidence is how it is discharged."
        ],
        "failureLooksLike": "A provider that lets traffic flow before verification looks like a convenience and is a liability: the enforcement lands later, on a live programme, and harder."
      }
    },
    {
      "id": "BRD-168",
      "slug": "brd-168",
      "title": "Consent must name the seller whose messages are delivered",
      "statement": "Where a reseller or agency sends, the consent must authorise messages delivered, or caused to be delivered, by the named seller.",
      "rationale": "Consent runs to a named party, and the FCC's definition covers messages a seller causes to be delivered as well as ones it sends itself — so consent naming the agency does not authorise the brand, and consent naming the brand does authorise its vendor. Getting this backwards is how a compliant-looking programme ends up with consent that covers nobody who is actually texting.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the consent text vs the sending entity",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Word the opt-in so it names the business the customer knows — the seller — and covers messages sent on its behalf: \"…to receive texts from Acme Coffee or its service providers.\" Done when the name in the consent is the brand on the registration.",
      "notes": "Applies where an agency or ISV sends on the brand's behalf. The condition lives in the criteria rather than in a tag, because no fact on the registration says who operates the sending — and the criteria open with the PASS boundary for the ordinary case where the brand sends for itself.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-169",
      "slug": "brd-169",
      "title": "Decide whether this brand is the seller or the telemarketer",
      "statement": "The brand's role — seller or telemarketer — must be established, because the obligations and the liability differ.",
      "rationale": "The two roles carry different duties, and a seller is liable for a vendor's failure to honour do-not-call requests even where the seller never touched the message. Businesses assume that engaging an agency transfers the exposure; it adds a second liable party instead. Knowing which side you are on decides who has to hold the consent records and who answers a complaint.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand's role classification",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write down which role this brand holds and make the consequences explicit in the vendor contract: who holds the consent records, who processes revocations, who answers a complaint. Done when both parties have the same answer in writing.",
      "notes": "A legal classification rather than a form field, so nothing about it can be validated at submission. The judgement flags which role the registration looks like; the user has to confirm it against their own contracts, because the answer decides where the liability sits rather than what to type.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-170",
      "slug": "brd-170",
      "title": "An agency arrangement does not shield either party in Virginia",
      "statement": "Virginia holds the telephone solicitor and the seller jointly liable, and an agency arrangement does not shield either from the other's failure.",
      "rationale": "Joint liability means both parties answer for the same violation, so the usual contractual comfort — the vendor indemnifies the brand, or the brand disclaims the vendor — allocates cost between them without reducing either one's exposure to the regulator. Businesses discover this after a complaint, when both names are on it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand's role classification",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Virginia SB 1339"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where messages reach Virginia numbers, treat compliance as a shared duty rather than a delegated one: both parties keep consent records, and both can evidence how revocations are processed. Done when either party could answer a complaint without the other.",
      "notes": "A state-law exposure rather than a registration check, and nothing in a submission reveals where the recipients are. What the user has to do is establish whether their list reaches Virginia numbers and, if it does, make sure their own records would stand up on their own — an indemnity from a vendor is not a defence to the regulator.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does your list reach Virginia numbers — and if so, could each party answer a complaint from its own records, without the other?",
        "howToCheck": [
          "Check the list for Virginia area codes and recipient states.",
          "Where it reaches them, confirm both the seller and the solicitor hold consent records and can evidence how revocations are processed.",
          "Read your vendor contract for what it actually does: an indemnity allocates cost between the parties, it is not a defence to the regulator."
        ],
        "failureLooksLike": "Both names are on the complaint. The contractual comfort each party relied on turns out to have reduced neither party's exposure."
      }
    },
    {
      "id": "BRD-172",
      "slug": "brd-172",
      "title": "Document every Texas exemption you rely on",
      "statement": "Where a Texas exemption is relied upon, the basis for it must be documented and retained.",
      "rationale": "Texas exemptions — publicly traded, insurer, supervised financial institution, FCC-regulated, non-profit, existing customer, majority brick-and-mortar — are affirmative defences, which means the burden of proving one sits with the business claiming it. An exemption that was true and undocumented is worth nothing at the point it is needed.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "Texas exemption evidence",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Texas SB 140"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "For each exemption relied on, keep a short written record: which exemption, why it applies, and the evidence — the licence number, the registration, the customer relationship. Review it annually, because several of the exemptions can lapse without anyone noticing.",
      "notes": "Nothing in a registration says which exemptions a business relies on. The user has to identify them and write the basis down while it is easy to establish — the exemptions are affirmative defences, so an undocumented one fails at exactly the moment it matters.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "For each Texas exemption you rely on, is there a written record of which exemption, why it applies, and the evidence behind it?",
        "howToCheck": [
          "List the exemptions in play: publicly traded, insurer, supervised financial institution, FCC-regulated, non-profit, existing customer, majority brick-and-mortar.",
          "For each, write down the basis and the evidence — the licence number, the registration, the customer relationship — while it is still easy to establish.",
          "Review annually. Several of these lapse without anyone noticing."
        ],
        "failureLooksLike": "The exemption was true and undocumented. It is an affirmative defence, so the burden sits with you, and it fails at precisely the moment it is needed."
      }
    },
    {
      "id": "BRD-173",
      "slug": "brd-173",
      "title": "One brand per tax ID",
      "statement": "At most one brand may be registered against a given tax ID.",
      "rationale": "The tax ID is what the carriers attribute reputation and volume against, so a second brand on the same EIN splits the identity of one business in two and pools its allowances anyway (BRD-235). Businesses create the second one for entirely sensible reasons — a new division, a new agency, a fresh start after a rejection — and the duplicate is refused against a record they cannot see.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein across the registry",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "Bird",
        "SignalWire",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "711",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30898",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Run several campaigns under one brand rather than several brands under one EIN — the campaign is the unit that describes a programme, and there is no penalty for having many. Where a second brand is genuinely needed, at least one provider will accept it with a written business justification; ask before submitting.",
      "notes": "Absorbs BRD-176, the tenant-local case of the same uniqueness rule. The strictest model is kept: several providers allow exactly one brand per EIN, and one allows a second with written justification. What the user has to establish is whether a brand already exists on this EIN anywhere — including at a former agency — because the refusal will not say where the existing one is.",
      "catalogIds": [
        "BRD-176"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does a brand already exist against this tax ID anywhere — at any provider, including one a former agency used?",
        "howToCheck": [
          "Ask the customer who has sent SMS on their behalf, ever. The refusal will not tell you where the existing brand is.",
          "Plan for one brand per legal entity and several campaigns beneath it — there is no penalty for many campaigns.",
          "Where a second brand is genuinely needed, at least one provider accepts it with a written business justification. Ask before submitting."
        ],
        "failureLooksLike": "A new division, a new agency, or a fresh start after a rejection produces a second brand on the same EIN. It is refused against a record the business cannot see, and the allowances were pooled anyway."
      }
    },
    {
      "id": "BRD-174",
      "slug": "brd-174",
      "title": "Per-EIN brand counts are capped even where several are allowed",
      "statement": "Where a provider permits more than one brand per tax ID, the number is still capped and each additional brand needs a documented business reason.",
      "rationale": "The looser model is not an open door: the cap exists because a single business fragmented across many brands defeats the reputation model the framework runs on. Agencies reach the cap by creating a brand per client campaign rather than per client, and the refusal arrives on the one that mattered.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein across the registry",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30898",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep one brand per legal entity and use campaigns for everything below that. Where several brands on one tax ID are genuinely required, write the business reason down before submitting — you will be asked for it, and after a refusal it is too late.",
      "notes": "Absorbs OPS-366, which states the same minimisation duty and adds the warning against resubmitting after an EIN-reuse rejection — a second attempt on the same EIN is read as evasion rather than as a retry. The count is registry-wide and invisible to us; what the user has to do is keep their own tally of brands registered against each EIN, across every provider they use.",
      "catalogIds": [
        "BRD-175",
        "OPS-366"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you keep your own tally of brands registered against each tax ID, across every provider you use?",
        "howToCheck": [
          "Count them. The registry-wide number is invisible to you and to us.",
          "Keep one brand per legal entity and use campaigns for everything below that.",
          "Where several are genuinely required, write the business reason down before submitting — you will be asked, and after a refusal it is too late."
        ],
        "failureLooksLike": "An agency creates a brand per client campaign rather than per client, hits the cap, and the refusal lands on the one that mattered. A second attempt on the same EIN after a reuse rejection is read as evasion rather than as a retry."
      }
    },
    {
      "id": "BRD-177",
      "slug": "brd-177",
      "title": "A duplicate on any identity field blocks the submission",
      "statement": "A brand submission is blocked where it matches an existing brand on legal name, registration number, mobile number, representative phone, email, website or stock symbol.",
      "rationale": "Duplicate detection runs across seven fields at once and reports the first collision it finds, so a registration can be refused for a field nobody thought was an identifier — a shared support email, a website reused across locations. It fires before the per-field rules, which is why the message is generic and the cause is usually not the field the team was looking at.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the seven-field duplicate key",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30703",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Before resubmitting, check all seven fields against any other brand your organisation has registered: legal name, registration number, mobile number, representative phone, contact email, website and stock symbol. Change the one that genuinely belongs to the other brand rather than varying a value to get past the check.",
      "notes": "The consolidated duplicate key; the per-field rules (BRD-011, BRD-050, BRD-107, BRD-135) remain separate because each has its own limit semantics — some allow reuse up to a count, this one allows none. The collision is against the whole registry. What the user has to do is enumerate their own brands first, since a collision with their own earlier registration is far more likely than one with a stranger.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you checked all seven duplicate-key fields against every other brand your organisation has registered?",
        "howToCheck": [
          "Enumerate your own brands first — a collision with your own earlier registration is far more likely than one with a stranger.",
          "Compare each of the seven: legal name, registration number, mobile number, representative phone, contact email, website, stock symbol.",
          "Change the value that genuinely belongs to the other brand. Do not vary a value to get past the check."
        ],
        "failureLooksLike": "The refusal is generic and fires before the per-field rules, so it names no field. The cause is usually a shared support mailbox or a website reused across locations — something nobody thought was an identifier."
      }
    },
    {
      "id": "BRD-178",
      "slug": "brd-178",
      "title": "Brand contact details must be unique across brands",
      "statement": "Each brand must have its own contact address, email and phone; reusing them across brands is a policy violation.",
      "rationale": "Shared contact details across many brands is the signature of a reseller registering client traffic under its own details, which is the pattern the brand-per-EIN and consent-transfer rules exist to stop. Providers treat it as a violation serious enough to trigger deactivation rather than a mere rejection.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email + brand.phone + brand.street",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicability": {
        "excludeEntityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a sole proprietor.",
      "universal": false,
      "remediation": "Use the client's own address, email and phone for each brand. If you are an agency, your details belong on the CSP record, never on the brand.",
      "example": "Brand contact: jane.doe@acmecoffee.com — not agency@northstarmarketing.com reused across ten brands.",
      "notes": "Detectable only against other brands in the same tenant, so it is surfaced as guidance until multi-brand state exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-181",
      "slug": "brd-181",
      "title": "A CSP has a default cap on how many brands it may register",
      "statement": "A CSP must not exceed the default cap of 1,000 registered brands without requesting an increase.",
      "rationale": "The cap is on the registering party rather than on any customer, so it is invisible to everyone whose brand is refused by it — a growing platform hits the ceiling and its next fifty customers see a rejection that has nothing to do with them. The increase is granted on request, which makes this pure operational hygiene: ask before you arrive.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the CSP brand count",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30712",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Track the brand count against the 1,000 cap and request an increase from TCR while there is headroom. Done when the increase is granted before the count reaches the ceiling rather than after registrations start failing.",
      "notes": "Absorbs OPS-364, which states the brand and campaign quotas together — BRD-182 is the per-brand half. The count belongs to the registering CSP rather than to any registration, so where it is not reported this reads as PASS with the cap stated: nothing in the submission indicates a breach, and the operator has to watch the number themselves.",
      "catalogIds": [
        "OPS-364"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Do you know your CSP brand count against the 1,000 cap, and have you requested an increase while there is still headroom?",
        "howToCheck": [
          "Track the count. It belongs to the registering party rather than to any customer, so nothing in a rejection will attribute it.",
          "Request the increase from TCR before the ceiling, not after registrations start failing — it is granted on request."
        ],
        "failureLooksLike": "A growing platform hits the ceiling and its next fifty customers see a rejection that has nothing to do with them."
      }
    },
    {
      "id": "BRD-182",
      "slug": "brd-182",
      "title": "A brand has a default cap on how many campaigns it may carry",
      "statement": "A brand must not exceed the default cap of 50 campaigns without requesting an increase.",
      "rationale": "Because the guidance everywhere else is \"use campaigns rather than more brands\", a large sender following that advice arrives at this ceiling — and the refusal lands on a campaign that was ready to launch. The increase is available on request; the problem is entirely that nobody asks until the cap bites.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the campaign count under the brand",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21732",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Retire campaigns you no longer send on, and request an increase before the count approaches 50. Done when the brand has headroom ahead of the next launch rather than at it.",
      "notes": "Where the campaign count is not reported this reads as PASS with the cap stated — nothing in the submission indicates a breach. The count is a property of the brand at the registry rather than of any one submission, so the operator has to watch it.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does this brand have campaign headroom under the 50-campaign cap ahead of the next launch?",
        "howToCheck": [
          "Count the campaigns on the brand and retire the ones you no longer send on.",
          "Request an increase before the count approaches 50 rather than at it."
        ],
        "failureLooksLike": "A large sender follows the advice to use campaigns rather than more brands, arrives at the ceiling, and the refusal lands on a campaign that was ready to launch."
      }
    },
    {
      "id": "BRD-186",
      "slug": "brd-186",
      "title": "Some providers lock a verified brand entirely",
      "statement": "On at least one provider, a brand's details become uneditable once it is verified.",
      "rationale": "Where the lock applies there is no correction path at all — a typo discovered after verification means a new brand and a new campaign, which is a very different plan from an edit. Teams assume the usual re-verification cost (BRD-044) applies and find the field simply will not take a new value.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand record after verification",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "SignalWire"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the brand carefully before it verifies, not after. Where an edit is required on a locked brand, plan a new registration and the migration of the campaigns under it, and keep the old brand live until the new one is verified.",
      "notes": "Conditional on the submission target, and not tagged with `providers` — the tag budget is reserved for baseline divergences and this is checkable from the operation itself. What the user has to establish is whether their own provider locks verified brands: the answer decides whether a late correction costs a re-verification or a rebuild.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does your provider lock a brand's details once it verifies?",
        "howToCheck": [
          "Ask the provider, or check its documentation, before the brand verifies — the answer decides whether a late correction costs a re-verification or a full rebuild.",
          "Where the lock applies, check the record carefully before verification and keep the old brand live if a replacement becomes necessary."
        ],
        "failureLooksLike": "A team assumes the usual re-verification cost and finds the field simply will not take a new value. A typo means a new brand and a new campaign."
      }
    },
    {
      "id": "BRD-189",
      "slug": "brd-189",
      "title": "A failed brand can be updated by API and not in the console",
      "statement": "Remediating a failed brand differs by interface: the API permits an update in place, while the console requires delete and recreate.",
      "rationale": "The same brand, the same fix, and two different procedures depending on where you are standing — so a team that reads the API documentation and works in the console concludes the update is broken. Deleting and recreating is also the more expensive of the two, because a new brand starts its verification from the beginning and takes its campaigns with it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the remediation path",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30793",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fix a failed brand through the API, where an update in place is accepted, rather than deleting and recreating it in the console. Note that the update is accepted only while the brand is in a failed state — a brand that has not failed cannot be updated this way at all.",
      "notes": "Absorbs OPS-345, which states which brand states accept an update at all. Conditional on the interface rather than on the provider, so it is not `providers`-tagged. What the user has to know is that the console path is not the only one available: an API update avoids the rebuild entirely.",
      "catalogIds": [
        "OPS-345"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "If this brand has failed, are you fixing it through the API rather than deleting and recreating it in the console?",
        "howToCheck": [
          "Use the API update in place — the console path requires delete and recreate, which restarts verification and takes the campaigns with it.",
          "Note the update is accepted only while the brand is in a failed state; a brand that has not failed cannot be updated this way at all."
        ],
        "failureLooksLike": "A team reads the API documentation, works in the console, and concludes the update is broken — then rebuilds a brand that could have been edited."
      }
    },
    {
      "id": "BRD-191",
      "slug": "brd-191",
      "title": "The two bundle identifiers must not be transposed",
      "statement": "The customer-profile and A2P bundle identifiers must be distinct, belong to the same account, and not be swapped.",
      "rationale": "The two identifiers have the same prefix and the same length, sit next to each other in the request, and describe different objects — so transposing them produces a submission that validates structurally and fails on a message about bundle validation. It is one of the very few brand errors that is genuinely a typo rather than a misunderstanding.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the bundle identifiers",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30793",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Read both identifiers back from the account and confirm which is the customer profile and which is the A2P bundle before resubmitting. Fix the upstream profile first if it is the one at fault — updating the brand alone re-fails for the same reason. Done when the two are distinct and each is in its own field.",
      "example": "customer_profile_bundle_sid: BU1111… · a2p_profile_bundle_sid: BU2222… — two different identifiers, each in its own field",
      "notes": "Absorbs OPS-346, which adds that the upstream profile must be corrected first. Only the shape and the distinctness are checkable here; whether each identifier points at the right object needs the account, which is external.",
      "catalogIds": [
        "OPS-346"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-194",
      "slug": "brd-194",
      "title": "Subscribe to brand status events instead of polling",
      "statement": "Brand status changes should be received as events on a webhook rather than discovered by polling.",
      "rationale": "Brand verification is asynchronous and can take days, and the event carries the granular failure reason that the status field alone does not — so a poller sees a state change with no explanation and has to guess at the cause. It also determines how quickly the campaign goes in behind it: an event-driven flow submits the moment the brand is ready, and a nightly poll can waste most of a day.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the webhook configuration",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Subscribe to the brand identity status event and read the failure reason from its payload. Wait for the event before making the next call rather than polling on a timer. Done when a brand state change arrives as a webhook carrying the reason.",
      "notes": "Absorbs BRD-193, which names the webhook carrying the granular failure reason, and OPS-347, which adds waiting for the event before the next call. The webhook belongs to the integration rather than to a registration, so where it is not reported this is undecided rather than passing — the product should ask rather than assume.",
      "catalogIds": [
        "BRD-193",
        "OPS-347"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Are brand status changes arriving on a webhook that carries the failure reason, rather than being discovered by polling?",
        "howToCheck": [
          "Subscribe to the brand identity status event and read the granular failure reason from its payload — the status field alone does not carry it.",
          "Wait for the event before making the next call rather than polling on a timer."
        ],
        "failureLooksLike": "A poller sees a state change with no explanation and guesses at the cause, and a nightly poll wastes most of a day before the campaign goes in behind it."
      }
    },
    {
      "id": "BRD-195",
      "slug": "brd-195",
      "title": "Deactivate every campaign before deleting a brand",
      "statement": "All campaigns on a brand must be deactivated before the brand can be deleted.",
      "rationale": "The deletion is refused while campaigns are attached, which is a good outcome — the alternative would be live traffic against a brand that no longer exists. It matters because the refusal arrives in the middle of a decommissioning that has usually already been announced, and the campaigns have their own minimum age before they can be deactivated.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the campaign states under the brand",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30710",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Deactivate each campaign first, allow for the campaign minimum age before it can be deactivated, and delete the brand last. Done when the brand carries no active campaigns at the moment of deletion.",
      "notes": "Absorbs OPS-169, which states the same precondition and adds the campaign minimum age — a campaign registered days ago may not be deactivatable yet, which turns a same-day decommissioning into a multi-day one.",
      "catalogIds": [
        "OPS-169"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is every campaign on this brand already deactivated, and has each cleared its minimum age?",
        "howToCheck": [
          "Deactivate the campaigns first, then delete the brand.",
          "Check each campaign's age — one registered days ago may not be deactivatable yet, which turns a same-day decommissioning into a multi-day one."
        ],
        "failureLooksLike": "The deletion is refused mid-decommissioning, after the date has already been announced, because a campaign registered last week cannot yet be switched off."
      }
    },
    {
      "id": "BRD-197",
      "slug": "brd-197",
      "title": "Brand deletion cannot be undone",
      "statement": "A deleted brand cannot be reactivated; the deletion is final.",
      "rationale": "There is no restore, so a brand deleted to tidy an account takes its verification, its vetting and its history with it — and rebuilding means paying the fees again and waiting out the verification again. Deletion is offered next to deactivation in most interfaces, and only one of the two is reversible.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the delete request",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Deactivate the campaigns and leave the brand in place unless you are certain. If the goal is to stop traffic, that is what deactivation is for; deletion is only for a brand you will never use again. Done when the decision is deliberate rather than housekeeping.",
      "notes": "The registry offers no restore path and neither does any provider. What the user has to weigh before confirming is the cost of rebuilding: the verification fee, any vetting fee, the vetting lead time, and re-registering every campaign under a new brand.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Are you certain this brand will never be used again — or do you actually want deactivation?",
        "howToCheck": [
          "If the goal is to stop traffic, deactivate. Deletion is final and there is no restore path at the registry or at any provider.",
          "Price the rebuild before confirming: the verification fee, any vetting fee, the vetting lead time, and re-registering every campaign under a new brand."
        ],
        "failureLooksLike": "A brand is deleted to tidy an account and takes its verification, its vetting and its history with it. Deletion sits next to deactivation in most interfaces and only one of the two is reversible."
      }
    },
    {
      "id": "BRD-199",
      "slug": "brd-199",
      "title": "Do not move suspended traffic onto a new brand",
      "statement": "Traffic under suspension or carrier review must not be migrated to a new brand or campaign.",
      "rationale": "Re-registering to escape a suspension is read as evasion rather than as remediation, and it escalates from a suspended campaign to a terminated account — a far worse outcome than the original finding. It is a natural move under pressure: the campaign is blocked, a new brand takes a day, and the send is due.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand or campaign migration",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN",
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21731",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Work the suspension where it is: answer the carrier review, correct what it identified, and appeal. Do not register a replacement brand or campaign while a review is open, and tell whoever is under deadline pressure why.",
      "notes": "Nothing in a new registration reveals that an old one is suspended, so this is a rule the user has to apply themselves. What they must do is check whether any open suspension or carrier review exists on the account before registering a replacement — and if one does, treat the registration as unavailable until it is resolved.",
      "catalogIds": [
        "OPS-147"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is there any open suspension or carrier review against this business right now?",
        "howToCheck": [
          "Check the account for open reviews before registering any replacement brand or campaign — nothing in a new registration reveals an old suspension.",
          "Where one is open, work it where it is: answer the review, correct what it identified, appeal.",
          "Tell whoever is under deadline pressure why a new registration is not the way out."
        ],
        "failureLooksLike": "The campaign is blocked, a new brand takes a day, and the send is due. Re-registering reads as evasion and escalates a suspended campaign into a terminated account."
      }
    },
    {
      "id": "BRD-200",
      "slug": "brd-200",
      "title": "A suspension follows the tax ID to the next brand",
      "statement": "After a suspension, new brands created on the same tax ID should be expected to be restricted.",
      "rationale": "The suspension attaches to the EIN rather than to the brand or the account, so it follows the business across providers — which means switching CSP does not reset it, and the new provider cannot lift it either. Businesses change provider precisely because the old one suspended them, and arrive at the same restriction with a second onboarding already paid for.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.ein after a suspension",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA",
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21731",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Resolve the original suspension rather than moving. If the business genuinely has a new programme, expect the new brand to carry restrictions from the start and be ready to evidence what changed — the review will ask.",
      "notes": "Absorbs OPS-181, which states the same EIN-level scope across CSPs. Suspension history is not visible in a registration. What the user has to establish is whether this EIN has ever been suspended anywhere — the answer determines whether a new registration is worth attempting at all, and a provider change will not help.",
      "catalogIds": [
        "OPS-181"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has this tax ID ever been suspended, at any provider?",
        "howToCheck": [
          "Establish the suspension history for the EIN before starting a new registration. It attaches to the tax ID, not to the brand or the account, so changing provider does not reset it.",
          "Where a suspension exists, resolve it rather than moving — the new provider cannot lift it either.",
          "If the business genuinely has a new programme, be ready to evidence what changed. The review will ask."
        ],
        "failureLooksLike": "A business changes provider precisely because the old one suspended it, and arrives at the same restriction with a second onboarding already paid for."
      }
    },
    {
      "id": "BRD-203",
      "slug": "brd-203",
      "title": "The starter brand class is deprecated",
      "statement": "A deprecated starter-class brand must not be created or updated.",
      "rationale": "The class is disabled rather than discouraged, so both creation and update are refused — which means an existing starter brand cannot be corrected in place either, and the only route forward is migration to a standard class. Integrations built against the older documentation still send it, and the failure names the class rather than explaining that it is gone.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand class",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21726",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register as a standard or low-volume standard brand instead. Where a starter brand already exists, migrate it rather than trying to update it — updates to the class are refused along with creations. Done when the brand class is one that is still supported.",
      "example": "brand_class: LOW_VOLUME_STANDARD — the supported low-cost route, in place of the deprecated STARTER class",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-204",
      "slug": "brd-204",
      "title": "Test against mock brands, not real ones",
      "statement": "Non-production submissions should use mock brands so fees are not charged and duplicate detection is not polluted.",
      "rationale": "A test registration is a real registration: it charges the fee, it consumes the identity fields, and it lands in the duplicate index — so the real submission that follows collides with the rehearsal. Mock brands exist precisely for this and cost nothing, but they are opt-in and easy to miss during an integration.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.mock",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the mock flag on every non-production submission, and use test rather than real identity values inside it. Done when no fee is charged and the real EIN, website and email have not been consumed by a rehearsal.",
      "example": "mock: true on every integration and staging submission",
      "notes": "Absorbs OPS-350, which states the same testing practice and the constraints mock records carry — a mock brand does not verify and cannot carry live campaigns, so it proves the integration rather than the registration. Nothing in a submission says whether it is a rehearsal, so a real brand with no mock flag passes: the check reports only the shape it can see.",
      "catalogIds": [
        "OPS-350"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-207",
      "slug": "brd-207",
      "title": "Campaigns cannot be submitted while vetting is in review",
      "statement": "A campaign must not be submitted while the brand's vetting is still in review.",
      "rationale": "The campaign is evaluated against the brand's standing at the moment it arrives, so one submitted mid-review is judged against an unfinished brand and refused — and the refusal names the campaign, which sends people to rewrite copy that was never the problem. Teams do it because the vet takes days and the campaign is ready now.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet state",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Wait for the vet to reach a final state before submitting the campaign. Subscribe to the brand status event (BRD-194) rather than polling, so the campaign goes in the moment the brand is ready instead of the next time somebody checks.",
      "notes": "The wait is at the vetting provider and nothing on our side shortens it. What the user has to do is sequence the two submissions — brand first, campaign after the vet lands — because a campaign refused for this reason still consumes a submission and, on some providers, a fee.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Has the brand's vet reached a final state, rather than still being in review, at the moment the campaign goes in?",
        "howToCheck": [
          "Read the vet status on the brand immediately before submitting the campaign.",
          "Subscribe to the brand status event rather than polling, so the campaign goes in the moment the brand is ready instead of the next time somebody checks."
        ],
        "failureLooksLike": "The campaign is ready and the vet takes days, so it is submitted anyway. The refusal names the campaign, and copy that was never the problem gets rewritten."
      }
    },
    {
      "id": "BRD-208",
      "slug": "brd-208",
      "title": "A score below the threshold means fixing the data, not re-vetting",
      "statement": "Where the vetting score is below the carrier or use-case threshold, the underlying brand data must be corrected before a re-vet.",
      "rationale": "The score is computed from the brand data and the public record behind it, so re-running the same examination against the same inputs returns the same number — and the cooldown means you only get to find that out every three months. The productive move is unglamorous: correct the address, publish the phone number, fix the website, then re-vet.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vetting_score",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS",
        "Sinch",
        "Infobip"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Work the inputs the score reads: an exact address match, a phone that answers, a website that names the business, social profiles, a mailbox on the brand domain. Then re-vet once, after the cooldown. Done when the underlying data has changed before the second examination.",
      "notes": "The score and its threshold are held by the vetting provider. What the user has to do is treat a low score as a data problem rather than a verdict — most of the inputs are things they control, and BRD-211 lists the few that they do not.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you corrected the data the score reads — address, phone, website, mailbox, social profiles — before ordering another vet?",
        "howToCheck": [
          "Treat a low score as a data problem, not a verdict. Most of its inputs are things you control.",
          "Work them in order: an exact address match, a phone that answers, a website that names the business, social profiles, a mailbox on the brand domain.",
          "Then re-vet once, after the cooldown."
        ],
        "failureLooksLike": "The same examination is re-run against unchanged inputs and returns the same number — and the three-month cooldown means that is the only answer available until spring."
      }
    },
    {
      "id": "BRD-209",
      "slug": "brd-209",
      "title": "The score maps to specific throughput terms",
      "statement": "The vetting score determines the carrier class and daily throughput the brand receives, and 75 or above is the target.",
      "rationale": "Score bands are the mechanism the whole vetting purchase exists for, and they are steep: 75 and above buys the top class and 200,000 messages a day, while 25 to 49 buys 10,000 and 1 to 24 buys 2,000. Promising a customer volume before the score is known is promising something you do not control, and the gap between bands is a factor of twenty rather than a matter of degree.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vetting_score",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "AT&T",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Read the throughput off the band rather than off the fee: 75–100 gives AT&T Class A/B and T-Mobile Top at 200,000/day; 50–74 gives C/D and High Mid at 40,000; 25–49 gives Low Mid at 10,000; 1–24 gives Low at 2,000. Size the programme against the band you actually hold.",
      "notes": "Absorbs OPS-129, which states the same mapping and warns against promising terms the score cannot deliver. The score is issued by the vetting provider after the fact, so what the user has to do is avoid committing to a send volume until the band is known — and where a date is fixed, order the vet early enough to re-work the plan if the band comes in low.",
      "catalogIds": [
        "OPS-129"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you avoided committing to a send volume before the vetting band is known?",
        "howToCheck": [
          "Read the throughput off the band, not off the fee: 75–100 is 200,000 a day, 50–74 is 40,000, 25–49 is 10,000, 1–24 is 2,000.",
          "Where a send date is already fixed, order the vet early enough to re-plan if the band comes in low."
        ],
        "failureLooksLike": "A customer is promised a volume before the score exists. The gap between adjacent bands is a factor of twenty, so the shortfall is not a matter of degree."
      }
    },
    {
      "id": "BRD-210",
      "slug": "brd-210",
      "title": "A low trust score blocks or narrows campaign registration",
      "statement": "A provider trust score at or below the gate blocks campaign registration entirely, and one just above it restricts which use cases are available.",
      "rationale": "This is a second score, computed by the provider rather than by the vetting partner, and it gates the use cases a brand may register at all — so a brand can be verified, vetted, and still unable to register the campaign it exists for. The rejection names the carrier rather than the score, which sends teams to look at their campaign copy.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.trust_score",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where campaign registration is blocked with a carrier-qualification message, check the trust score before editing the campaign. Raising it means improving the brand data and, usually, buying a standard vet — the campaign is not the thing that failed.",
      "notes": "The trust score is the provider's own and is not exposed on the brand record we see. What the user has to do is ask their provider for the score when a campaign is refused on carrier qualification, because that one number explains a rejection that otherwise reads as a content problem.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "When a campaign is refused on carrier qualification, have you asked your provider for the brand's trust score before editing the campaign?",
        "howToCheck": [
          "Ask the provider directly for the trust score — it is theirs, computed separately from the vetting score, and it is not on the brand record you can see.",
          "Where the score is the cause, raise it by improving brand data and usually by buying a standard vet. The campaign is not the thing that failed."
        ],
        "failureLooksLike": "The rejection names the carrier rather than the score, so the team rewrites campaign copy for a week. A brand can be verified, vetted, and still barred from the use case it exists for."
      }
    },
    {
      "id": "BRD-211",
      "slug": "brd-211",
      "title": "Some score inputs cannot be fixed by editing the form",
      "statement": "Company size, years in operation and domain age drive the trust score and cannot be improved by data entry.",
      "rationale": "Once a business understands the score is data-driven, the reasonable next step is to improve the data — and several of the largest inputs are facts about the company's history rather than fields on a form. Saying so plainly stops a team spending a week polishing a record that was already accurate, and redirects them to the inputs that do move.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the firmographic profile",
      "severity": "LOW",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Accept the firmographic inputs and work the ones you control: an exact address match, a reachable phone, a website that describes the business, social profiles with history, a contact mailbox on the brand domain. For a genuinely new company, plan around the lower band rather than trying to raise it.",
      "notes": "Advisory, and it exists to stop wasted effort. What the user has to accept is that a young company with a new domain scores lower than an old one with the same paperwork — the route to volume for them is a campaign-level one, not a better-filled brand form.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you accept that company age, size and domain age set part of this score, and have you switched your effort to the inputs you control?",
        "howToCheck": [
          "Stop polishing a record that is already accurate. Company size, years in operation and domain age are facts about history, not fields.",
          "Work what does move: an exact address match, a reachable phone, a website that describes the business, social profiles with history, a contact mailbox on the brand domain.",
          "For a genuinely new company, plan around the lower band rather than trying to raise it."
        ],
        "failureLooksLike": "A week spent re-entering correct data. A young company with a new domain scores below an old one with identical paperwork, and the route to volume for it is campaign-level, not a better-filled form."
      }
    },
    {
      "id": "BRD-212",
      "slug": "brd-212",
      "title": "Vetting is not a remedy for a rejection",
      "statement": "External vetting exists to raise throughput, not to reverse a rejection, and it is not guaranteed to raise anything.",
      "rationale": "The instinct after a refusal is to buy the most expensive verification available, and it is precisely the wrong move: the vet examines the same data that was just refused, so it returns the same answer and charges for it. What fixes a rejection is correcting the brand data or filing an appeal — and a vet ordered on uncorrected data is money spent to confirm the problem.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the reason a vet is being ordered",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Correct the brand data the rejection pointed at, or file an identity appeal with documentation. Order the vet afterwards, and only if the throughput you need is above what a verified brand gets by default. Done when the identity status is resolved before any vet is bought.",
      "example": "Fix the legal name to match the CP-575, then re-verify — rather than ordering a standard vet against the same unchanged name",
      "notes": "Absorbs OPS-305, which states the same two facts: vetting is not a rejection remedy, and a top standard score already buys the best available terms.",
      "catalogIds": [
        "OPS-305"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-214",
      "slug": "brd-214",
      "title": "An enhanced vet buys nothing on top of a top standard score",
      "statement": "An enhanced vet must not be ordered where the standard vet has already returned a score at the top band.",
      "rationale": "Throughput terms are set by score bands, and the top band is the top band — a higher-grade vet against a score already at 75 or above produces the same carrier terms for a second fee. It is bought in good faith by teams who read \"enhanced\" as \"better\" rather than as \"a different examination of the same company\".",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vetting_score + vet class",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep the standard vet. If throughput is still short of what the programme needs, the constraint is the carrier tier rather than the vet class, and the route up is campaign-level rather than another purchase. Done when no second vet is ordered against a score of 75 or more.",
      "example": "vetting: STANDARD, score 82 — and no enhanced vet ordered on top of it",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-215",
      "slug": "brd-215",
      "title": "The vetting provider must support this entity type and country",
      "statement": "The chosen vetting provider must support the brand's entity type and country of registration.",
      "rationale": "The providers cover different ground — one takes public, private, non-profit and government brands, another only US private and public companies, and the political route is import-only — so an order placed with the wrong one is refused after it is paid for. Nothing in the ordering flow narrows the list to the ones that can actually take your brand.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "vet provider vs brand.entity_type / brand.country",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "525",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the provider's coverage before ordering: Aegis takes public, private, non-profit and government brands; WMC takes US private and public companies only; Campaign Verify is political and import-only. Done when the provider you order from lists your entity type and country.",
      "example": "vetting provider: AEGIS for a NON_PROFIT brand — WMC does not cover non-profits",
      "notes": "The coverage matrix is published by TCR and changes as providers are added. Only the two documented exclusions are checked here — WMC's US-private-and-public-only scope, and Campaign Verify being import-only — so a provider passing this check should still be confirmed against the current matrix.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-216",
      "slug": "brd-216",
      "title": "An imported vet must match the brand exactly, and carry its token",
      "statement": "On a vet import, the legal name and entity type must match the vetting report exactly, and the vetting token must be supplied.",
      "rationale": "The import attaches somebody else's examination to this brand, so the registry checks that the two describe the same company before accepting it — and it compares strings. A report issued to \"Acme Coffee Company\" cannot be imported onto a brand registered as \"Acme Coffee Co, LLC\" even though everyone involved knows they are the same business.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet import payload",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "538",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "520",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Compare the report against the brand field by field before importing, and correct whichever is wrong — usually the brand, since the report reflects what the vetting provider verified. Supply the verification certificate token with the import. Done when name and entity type match character for character and the token is attached.",
      "example": "vet report legal name: Acme Coffee Co, LLC · brand company_name: Acme Coffee Co, LLC · token: supplied",
      "pitfalls": [
        "Correcting the brand name to match the report is itself an identity change (BRD-044), so do it before the import rather than during it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-217",
      "slug": "brd-217",
      "title": "Some vet classes cannot be imported at all",
      "statement": "An Authentication+ class vet must not be submitted for import — the class does not support it.",
      "rationale": "Authentication+ verifies a live second factor rather than producing a transferable report, so there is nothing to import and the attempt is refused rather than queued. It is a reasonable thing to try: every other class can be imported, and the API takes the same shape for all of them.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet import class",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "525",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Order the Authentication+ vet directly on the brand instead of importing one. Where you already hold a report from elsewhere, only the standard and political classes can be brought across. Done when no import is attempted for the Authentication+ class.",
      "example": "vet import class: STANDARD — Authentication+ is ordered directly on the brand instead",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-218",
      "slug": "brd-218",
      "title": "Import the same token into every instance of the same brand",
      "statement": "Where the same brand is registered by more than one CSP, the same vetting token must be imported into each instance.",
      "rationale": "Throughput follows the vet, and the vet is attached to a brand instance rather than to the business — so a brand registered through two CSPs with the token imported into only one of them sends fast on one route and slowly on the other, for no visible reason. Only the Aegis standard token can be reused this way; every other partner issues single-use tokens.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet token across brand instances",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Import the same Aegis standard token into each duplicate brand instance, rather than ordering a fresh vet per CSP. Done when every instance of the brand reports the same vetting score.",
      "example": "vetting token: the same Aegis certificate imported into both the Twilio and the Telnyx brand instances",
      "pitfalls": [
        "Political tokens are single-use (BRD-246) and cannot be spread this way — the reuse rule is specific to the Aegis standard class."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-221",
      "slug": "brd-221",
      "title": "Respect the cooldown before re-ordering a vet",
      "statement": "A vet must not be re-ordered against unchanged brand information, nor more than once in three months after the first post-registration re-vet.",
      "rationale": "A re-vet examines the same records, so ordering one against unchanged data returns the same score and charges for it — which is why the registry rate-limits the attempt rather than trusting people not to make it. The cooldown catches teams who are working the problem hard: three orders in a fortnight after a disappointing score is a very natural response and none of them can succeed.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the re-vet request + the brand diff",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "525",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Change the underlying brand data first — the firmographic inputs a vet reads — and then wait out the three months. If the score was the problem, the productive move is correcting the address, the website and the contact details rather than re-running the same examination.",
      "notes": "Merges the per-class cooldowns the catalog records separately (BRD-219 for RCS/RBM vetting, BRD-220 for the Telnyx window) into the one rule they describe: re-ordering inside any of them is refused or wasted. The window is enforced at the registry against a history we only see when the caller supplies it, so a re-order with no date attached is reported as undecided rather than allowed.",
      "catalogIds": [
        "BRD-219",
        "BRD-220"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Has the brand data behind the score actually changed since the last vet, and is the three-month cooldown clear?",
        "howToCheck": [
          "Find the date of the last vet order. Inside the window, a re-order is refused or wasted.",
          "Before re-ordering, change what the vet reads: the address, the website, the published phone number, the contact details.",
          "Three orders in a fortnight after a disappointing score is a natural response and none of them can succeed."
        ],
        "failureLooksLike": "The same examination is re-run against the same records and returns the same number, having charged for it — and the cooldown means you find out every three months."
      }
    },
    {
      "id": "BRD-222",
      "slug": "brd-222",
      "title": "Editing the brand expires a completed vet",
      "statement": "A completed vet expires when the brand information behind it changes.",
      "rationale": "The vet certifies the brand as it stood, so an edit invalidates it in the same way a change of address invalidates an identity document — and the expiry is silent. Throughput drops back to the unvetted tier and the first anyone knows is a queue building on a send that used to clear.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand diff vs the vet state",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Batch brand edits and make them before ordering the vet, not after. Where an edit is unavoidable on a vetted brand, budget for re-ordering the vet and expect the cooldown in BRD-221 to apply to it.",
      "notes": "The expiry happens at the registry when the change lands. What the user has to do is treat a completed vet as pinned to the current brand record: check whether an edit is worth losing it before making it, because getting it back costs the fee again and the time again.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is this brand edit worth losing the completed vet that is pinned to the current record?",
        "howToCheck": [
          "Check whether a completed vet is attached before making the edit.",
          "Batch edits and make them before ordering the vet, not after.",
          "Where the edit is unavoidable, budget for re-ordering the vet and expect the three-month cooldown to apply."
        ],
        "failureLooksLike": "The expiry is silent. Throughput drops to the unvetted tier and the first anyone knows is a queue building on a send that used to clear."
      }
    },
    {
      "id": "BRD-223",
      "slug": "brd-223",
      "title": "Website and contact email must be in place before an identity-plus vet",
      "statement": "The brand website and business contact email must be populated before an Authentication+ or RBM vet is ordered.",
      "rationale": "These vets verify the brand through its own domain and mailbox, so ordering one before either exists submits an examination with nothing to examine — it is refused on a missing field after the fee is taken. The two fields are optional on an ordinary brand, which is exactly why they are empty at the moment somebody decides to order the vet.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.website + brand.business_contact_email",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate the brand website and the business contact email, and let the update settle on the brand record, before placing the vet order. Done when both fields read back from the brand and the mailbox has been tested (BRD-119).",
      "example": "website: https://acmecoffee.com · business_contact_email: jane.doe@acmecoffee.com — both present before the order",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-224",
      "slug": "brd-224",
      "title": "Public companies must clear Authentication+ before campaigns",
      "statement": "A PUBLIC_PROFIT brand must reach identity VERIFIED with Authentication+ ACTIVE before any campaign can be registered.",
      "rationale": "Authentication+ is a two-factor identity confirmation sent to the registered business contact, and it gates campaign creation for public companies specifically. Vendor guidance frequently describes TCR 2FA as universal; AWS is explicit that it applies only to PUBLIC_PROFIT, so applying it everywhere wastes time and applying it nowhere blocks public brands.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.identity_status",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "AWS"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Complete TCR Authentication+ (two-factor identity confirmation) for the brand before submitting campaigns.",
      "notes": "AWS is explicit that TCR 2FA applies only to PUBLIC_PROFIT. Vendor blogs routinely describe it as universal — it is not.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the brand show identity VERIFIED with an Authentication+ vet recorded as ACTIVE, before any campaign is submitted?",
        "howToCheck": [
          "Open the brand record at your provider and read two things: the identity status, and the list of vets attached to it.",
          "Confirm an Authentication+ (two-factor identity) vet is present and ACTIVE rather than merely ordered."
        ],
        "failureLooksLike": "The brand reads VERIFIED and looks finished, but carries no Authentication+ vet. Every campaign against it is refused, and the refusal names the campaign rather than the missing vet."
      }
    },
    {
      "id": "BRD-225",
      "slug": "brd-225",
      "title": "Authentication+ must now be ordered explicitly",
      "statement": "Under Authentication+ 2.0 the vet must be ordered explicitly after the identity check; it no longer runs automatically.",
      "rationale": "It used to be automatic, so every runbook, integration and mental model built before the change assumes it will happen on its own. It does not: the brand verifies, nobody orders the vet, and the public-company brand sits without the Authentication+ status that gates its campaigns — with no error anywhere, because nothing failed.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Authentication+ vet order",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "After the identity check completes, place the Authentication+ order explicitly and watch for the 2FA mail. Add the order as a step in your onboarding runbook — its absence produces no error, so nothing will remind you.",
      "notes": "The order is an action at the registry rather than a field on the registration. What the user has to do is remember it: a public brand that verified but was never Authentication+ vetted looks healthy and cannot register campaigns.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Has the Authentication+ vet been ordered explicitly, as a step in your runbook, after the identity check completed?",
        "howToCheck": [
          "Open the brand and confirm an Authentication+ order exists. It used to run automatically and no longer does.",
          "Add the order to the onboarding runbook — its absence produces no error, so nothing will remind you.",
          "Then watch the business contact mailbox for the 2FA mail."
        ],
        "failureLooksLike": "The brand verifies and looks healthy. No vet was ordered, every campaign is refused, and nothing anywhere reports a failure — because nothing failed."
      }
    },
    {
      "id": "BRD-226",
      "slug": "brd-226",
      "title": "The 2FA PIN must be completed inside the vet's window",
      "statement": "The business contact must complete the Authentication+ 2FA PIN within 30 days of the vet being submitted, or the vet must be re-submitted.",
      "rationale": "The window belongs to the vet rather than to the email, and it runs whether or not anyone saw the message — so a vet ordered before a quiet period expires quietly, and the fee goes with it. Public companies, whose contact is usually a senior person with a filtered mailbox, are the ones this catches.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Authentication+ 2FA state",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "565",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "21737",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Tell the named contact the mail is coming, before it is sent, and get their confirmation that they have completed the PIN. Track the 30-day deadline from the order date rather than from the mail date. Done when the brand reports Authentication+ active.",
      "notes": "The window is 30 days for the vet and 7 for the emailed link inside it (BRD-227); both are live and neither is visible to us. What the user has to do is own the deadline themselves — brief the contact before ordering, and chase inside the first week rather than the fourth.",
      "catalogIds": [
        "OPS-306"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has the named contact confirmed to you, in words, that they completed the PIN?",
        "howToCheck": [
          "Brief the contact before the vet is ordered, not after the mail is sent.",
          "Track the 30-day deadline from the order date, not from the mail date, and chase inside the first week rather than the fourth.",
          "Done when the brand itself reports Authentication+ active."
        ],
        "failureLooksLike": "A vet ordered before a quiet period expires quietly, taking the fee with it. The contact is usually a senior person with a filtered mailbox who never saw the message."
      }
    },
    {
      "id": "BRD-227",
      "slug": "brd-227",
      "title": "The emailed verification link expires after a week",
      "statement": "The Authentication+ verification link and PIN expire after seven days and must be re-requested.",
      "rationale": "Two different clocks run on the same vet: thirty days for the vet, seven for the mail inside it — so the link dies three weeks before the vet does, and a contact who returns from leave to find the message finds a link that no longer works. Requesting a fresh one is simple and nothing tells you that it is what you need.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Authentication+ email link",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS",
        "Vonage",
        "Telnyx",
        "Aegis"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Re-request the verification mail rather than waiting on the old one, and have the contact complete it the same day it arrives. Done when the PIN is accepted and the brand reports Authentication+ active.",
      "notes": "The seven-day figure comes from the CPaaS documentation and is marked UNVERIFIED against a secondary source in the research; the thirty-day vet window (BRD-226) is TCR's own and is firm. What the user has to do is treat the link as short-lived whichever number is right: request it when the contact is available, not before.",
      "catalogIds": [
        "OPS-307"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Will the contact be available to complete the PIN the same day the mail arrives?",
        "howToCheck": [
          "Request the verification mail when the contact is at their desk — the link is short-lived, roughly seven days inside a thirty-day vet window.",
          "If they return from leave to a dead link, re-request a fresh mail rather than waiting on the old one. Nothing tells you that is what you need."
        ],
        "failureLooksLike": "The link dies three weeks before the vet does. The contact finds the message, clicks it, and gets an error that suggests the whole vet has failed."
      }
    },
    {
      "id": "BRD-228",
      "slug": "brd-228",
      "title": "Do not queue a second Authentication+ vet behind a pending one",
      "statement": "A second Authentication+ vet must not be submitted while one is pending.",
      "rationale": "The second order is refused rather than queued, so the natural response to a vet that seems stuck — order another one — costs a fee and achieves nothing. It seems stuck because the 2FA has not been answered yet, which is a different problem with a different fix.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Authentication+ vet queue",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "525",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Check whether the 2FA mail has been answered before ordering anything. If the link has expired, re-request it (BRD-227); if the window has passed entirely, the existing vet has to fail before a new one can be placed. Done when only one Authentication+ vet is in flight.",
      "example": "One Authentication+ vet in flight — chase the 2FA mail rather than placing a second order",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-229",
      "slug": "brd-229",
      "title": "An unanswered 2FA is not an appealable failure",
      "statement": "An Authentication+ failure caused by an unanswered 2FA email must be resolved with a new vet rather than an appeal.",
      "rationale": "An appeal asks a reviewer to look again at a decision, and there was no decision here — the examination never completed because nobody answered the mail. The appeal is refused, the time is lost, and the vet still has to be re-ordered at the end of it. Appealing is the natural instinct because the outcome reads as a failure.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Authentication+ failure reason",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Order a new Authentication+ vet, with the contact briefed and available this time. Save the appeal for a vet that ran and reached a conclusion you disagree with. Done when a fresh vet is in flight rather than an appeal.",
      "example": "A new Authentication+ order — not an appeal, where the failure reason is an unanswered or expired 2FA",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-230",
      "slug": "brd-230",
      "title": "Identity-plus vetting is for public companies only",
      "statement": "An Authentication+ vet must not be ordered for a brand that is not PUBLIC_PROFIT.",
      "rationale": "Authentication+ is the two-factor confirmation designed for listed companies, and the registry refuses the order for any other entity type rather than downgrading it to something available. Teams order it because it is the strongest-sounding option on the list, and the fee is taken before the refusal.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet order + brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "592",
          "remediable": true
        }
      ],
      "applicability": {
        "excludeEntityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is NOT a public company.",
      "universal": false,
      "remediation": "Order a standard vet instead — it is the one that raises throughput for a private, non-profit or government brand. Authentication+ is not a higher tier of the same thing; it is a different verification for a different entity type.",
      "example": "vetting class: STANDARD for a PRIVATE_PROFIT brand — not AUTHPLUS",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-231",
      "slug": "brd-231",
      "title": "Identity information is frozen once Authentication+ completes",
      "statement": "After Authentication+ verification completes, brand identity information cannot be changed; a new brand must be created instead.",
      "rationale": "The completed verification is bound to the identity it examined, and the registry will not let that identity move underneath it — so an edit is refused rather than applied, and the only route to a corrected record is a new brand and a new verification. Discovering that after the campaigns are live is the expensive version, because they have to be rebuilt under the new brand.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the identity fields after Authentication+",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "remediation": "Get the identity fields right before ordering Authentication+ — the legal name in particular, since that is the one people correct later. Where a change is genuinely required afterwards, plan a new brand and the migration of every campaign under it.",
      "notes": "The freeze is enforced at the registry. What the user has to do is treat the Authentication+ order as the point of no return for the identity fields, and run the checks in BRD-002, BRD-003 and BRD-093 before placing it rather than after.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Are the identity fields — legal name above all — confirmed correct before you place the Authentication+ order?",
        "howToCheck": [
          "Run the identity checks first: the IRS name match, the EIN, and the address against the registration record.",
          "Treat the Authentication+ order as the point of no return: afterwards the identity cannot be edited, only rebuilt as a new brand with every campaign migrated under it."
        ],
        "failureLooksLike": "A legal name is corrected after the vet completes. The edit is refused rather than applied, and the only route to a right record is a new brand and a rebuild of the live campaigns."
      }
    },
    {
      "id": "BRD-232",
      "slug": "brd-232",
      "title": "Only three registration shapes may skip the automatic vet",
      "statement": "The skip-automatic-vetting flag may be set only for Low-Volume Standard registrations, 527 political organisations, and Campaign-Verify-registered brands.",
      "rationale": "The flag suppresses the secondary vet that would otherwise run, and it exists for the three cases where that vet is either unavailable or already discharged by another artefact. Setting it anywhere else does not save a fee — it produces a brand that skipped its verification and lands at the lowest throughput with no finding to explain why.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.skip_automatic_sec_vet",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Leave the flag unset unless the brand is Low-Volume Standard, a 527 organisation, or registered through Campaign Verify. If it was set to avoid a fee, clear it and accept the automatic vet — the throughput it buys is worth more than the fee it costs.",
      "example": "skip_automatic_sec_vet: false — unless brand_class is LOW_VOLUME_STANDARD, 527 or CAMPAIGN_VERIFY",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-233",
      "slug": "brd-233",
      "title": "Some providers require vetting for every use case above the lowest tier",
      "statement": "At least one provider requires standard or enhanced brand vetting for any use case beyond the lowest-volume mixed tier.",
      "rationale": "Where this applies, vetting stops being an upgrade and becomes a precondition for the use case itself — a marketing campaign simply cannot be registered without it. It is not visible in any field validation, so a team that budgeted for registration and not for vetting discovers the gap at the point the campaign is refused.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet record + campaign.use_case",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Vonage"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check whether your provider gates use cases on vetting before quoting a customer, and order the vet as part of onboarding rather than as a later upgrade. Done when a vet is held before the campaign is submitted on a provider that requires one.",
      "notes": "Conditional on the submission target and stated by Vonage explicitly. What the user has to establish is whether their own provider imposes it — the answer is in the onboarding terms rather than in an error code, and finding it out afterwards costs the campaign schedule rather than the fee.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you checked whether your provider gates use cases on vetting, before quoting the customer?",
        "howToCheck": [
          "Read the provider's onboarding terms for a vetting precondition on anything above the lowest-volume mixed tier — it produces no field validation error.",
          "Where it applies, order the vet as part of onboarding rather than as a later upgrade."
        ],
        "failureLooksLike": "A team budgets for registration and not for vetting, and finds the gap when the marketing campaign is refused — with the schedule, not the fee, as the thing that is lost."
      }
    },
    {
      "id": "BRD-234",
      "slug": "brd-234",
      "title": "Vet before the campaign, not after the volume disappoints",
      "statement": "External vetting should be ordered before registering a campaign that needs material volume.",
      "rationale": "An unvetted brand starts at the bottom carrier tier — a couple of thousand messages a day — and the tier is what the campaign inherits at registration. Ordering the vet afterwards works, but the launch has already been scheduled against throughput the brand does not have, and the vet takes days. The cost of getting this wrong is a missed send date rather than a rejection, which is why nothing flags it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet record",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Decide the daily volume the programme needs, then order the vet if it is above the unvetted floor of roughly 2,000 messages a day. Do it before the campaign is registered, and allow several days for the result.",
      "notes": "Whether a vet was ordered is a fact about the account rather than about the registration, so this reports UNCLEAR rather than guessing. What the user has to do is the arithmetic: list size against the unvetted daily cap, and if the first exceeds the second, the vet is a prerequisite rather than an upgrade.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does the volume this programme needs exceed the unvetted daily floor of roughly 2,000 messages a day?",
        "howToCheck": [
          "Do the arithmetic: list size and send frequency against the unvetted cap.",
          "If the first exceeds the second, order the vet before registering the campaign and allow several days for the result — the campaign inherits the brand's tier at registration."
        ],
        "failureLooksLike": "Nothing is rejected. The launch is scheduled against throughput the brand does not have, and the vet that would fix it takes days that the send date does not allow."
      }
    },
    {
      "id": "BRD-235",
      "slug": "brd-235",
      "title": "The daily carrier cap is an EIN-level allowance, pooled across CSPs",
      "statement": "The T-Mobile daily brand cap is an allowance against the tax ID, pooled across every campaign and every CSP that registered it, resetting at midnight Pacific.",
      "rationale": "Because the cap attaches to the EIN rather than to the brand, splitting traffic across two providers does not double it — the two routes draw from one bucket, and the second one starts failing when the first has used the allowance. Teams add a provider specifically to increase capacity and get no more messages delivered, with the failures landing on whichever route sent last.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "daily volume against brand.ein",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Plan volume against one allowance per tax ID, not per provider or per campaign, and reconcile sends across every route before scheduling a large day. The window resets at midnight Pacific, which is the boundary to schedule around.",
      "notes": "Absorbs OPS-118, which states the same pooling behaviour. Volume is not visible at registration time. What the user has to do is add up the traffic across every CSP that holds a brand on this EIN — including ones another team set up — because the cap does not care that they are separate accounts.",
      "catalogIds": [
        "OPS-118"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you added up the daily traffic across every CSP and every campaign that shares this tax ID?",
        "howToCheck": [
          "List every provider holding a brand on this EIN — including accounts another team set up. The cap does not care that they are separate accounts.",
          "Reconcile the total against one allowance before scheduling a large day, and schedule around the midnight Pacific reset."
        ],
        "failureLooksLike": "A second provider is added specifically to increase capacity, and no more messages are delivered. Both routes draw on one bucket, and the failures land on whichever sent last."
      }
    },
    {
      "id": "BRD-237",
      "slug": "brd-237",
      "title": "Short-code assignment needs a successful vetting status",
      "statement": "A successful vetting status on the brand and the content provider is required to be assigned a short code or to extend a lease.",
      "rationale": "Short codes are leased on a longer cycle than 10DLC brands are vetted, so a lease renewal can arrive when the vet has lapsed — and the renewal is refused for a reason that has nothing to do with the code or the programme running on it. Losing a short code is disproportionately expensive, because the number is printed on things.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vetting status",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "Infobip"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the vetting status well before a lease renewal date and re-vet if it has lapsed. Done when a current successful vet is in place on both the brand and the content provider ahead of the renewal.",
      "effectiveFrom": "2025-03-31",
      "notes": "A short-code path requirement, recorded here because the vet is a brand-level artefact. What the user has to do is track the lease date against the vet date themselves — nothing connects the two, and the renewal window is short.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is a current successful vet in place on both the brand and the content provider, ahead of the short-code lease renewal date?",
        "howToCheck": [
          "Put the lease renewal date and the vet date side by side. Nothing connects the two automatically, and the renewal window is short.",
          "Re-vet before the renewal if the vet has lapsed."
        ],
        "failureLooksLike": "The renewal is refused for a reason that has nothing to do with the code or the programme on it — and the number is printed on packaging, vehicles and signage."
      }
    },
    {
      "id": "BRD-238",
      "slug": "brd-238",
      "title": "Re-vet annually",
      "statement": "Brand vetting must be renewed each year, with the 2FA verification completed again to the brand email on file.",
      "rationale": "A vet certifies the business as it was, and businesses move, rename and change hands — so the certification is time-boxed. The renewal goes to the email on file, which is often a person who has left, and the first sign that it lapsed is throughput dropping back to the unvetted tier.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet age",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Aegis",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Diary the re-vet a month before the anniversary and confirm the brand email still reaches somebody first. Done when the renewal completes before the existing vet expires, rather than after throughput drops.",
      "notes": "Flagged UNVERIFIED in the research — the annual cadence comes from a secondary source rather than from CTIA directly, so treat it as the prudent assumption rather than a published deadline. What the user has to do either way is keep the brand email monitored, since that is where the renewal lands.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do you know the vet's anniversary date, and does the brand email still reach somebody who will act on the renewal?",
        "howToCheck": [
          "Diary the re-vet a month before the anniversary.",
          "Check the brand email on file first — it is often a person who has left, and that is where the renewal and its 2FA land.",
          "Treat the annual cadence as the prudent assumption; the research flags it as a secondary source rather than a published deadline."
        ],
        "failureLooksLike": "The first sign the vet lapsed is throughput dropping back to the unvetted tier, weeks after the renewal mail went to a mailbox nobody reads."
      }
    },
    {
      "id": "BRD-241",
      "slug": "brd-241",
      "title": "Political vetting is satisfied by exactly one recognised artefact",
      "statement": "A political brand must hold exactly one of: a Campaign Verify token, an Aegis political vet, or verified 501(c)(3/4/5/6) status.",
      "rationale": "The three routes are alternatives rather than a checklist, and they are not interchangeable in cost or lead time — Campaign Verify is free and slow, an Aegis vet is paid and faster, and the tax-exempt route is instant if you already hold the status. Choosing the wrong one for the time available is how political campaigns miss their own dates.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the political vetting artefact",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "509",
          "remediable": true
        },
        {
          "provider": "Infobip",
          "code": "9001",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Pick the route that fits the calendar: 501(c)(4), (5) or (6) status qualifies immediately at some providers; a Campaign Verify token needs an FEC or state filing and takes days; an Aegis political vet is bought and couriers a PIN. Start whichever one you choose weeks before the send date.",
      "notes": "Which artefacts a brand holds is not on the registration. What the user has to do is choose the route deliberately and start it early — the Campaign Verify path in particular depends on a filing reference and on post, neither of which can be hurried.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you chosen one of the three political routes deliberately, and started it far enough ahead of the send date?",
        "howToCheck": [
          "Pick by calendar, not by cost: 501(c)(4), (5) or (6) status qualifies immediately at some providers; a Campaign Verify token needs an FEC or state filing and takes days; an Aegis political vet is bought and couriers a PIN.",
          "Start weeks before the send date. The Campaign Verify path depends on a filing reference and on post, and neither can be hurried."
        ],
        "failureLooksLike": "The cheapest route is chosen in the last fortnight of a cycle. It is also the slowest, and the campaign misses its own date."
      }
    },
    {
      "id": "BRD-242",
      "slug": "brd-242",
      "title": "A tax-exempt organisation must not order a political vet",
      "statement": "A political vet must not be requested or imported for a brand that already holds 501(c)(3), (4), (5) or (6) status.",
      "rationale": "The tax-exempt status is itself one of the three qualifying artefacts, so ordering a vet on top of it is disallowed rather than redundant — the request is refused. It is a natural mistake because the vet looks like the thorough option, and nothing in the ordering flow says the status you already hold has made you ineligible for it.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.tax_exempt_status + the political vet order",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "541",
          "remediable": true
        }
      ],
      "applicability": {
        "entityTypes": [
          "NON_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a non-profit.",
      "universal": false,
      "remediation": "Rely on the tax-exempt status you already hold and do not order or import a political vet. Make sure the status is recorded as an attribute on the brand rather than only claimed on the form — BRD-263 is the appeal route if the registry did not set it.",
      "example": "tax_exempt_status: 501(c)(4) with no political vet ordered — the status is the qualifying artefact",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-243",
      "slug": "brd-243",
      "title": "A 527 organisation needs a token and its filing reference on record",
      "statement": "A 527 political organisation must hold a Campaign Verify token, and must record the FEC ID or state filing URL used to obtain it.",
      "rationale": "The token proves the organisation was verified; the filing reference proves which organisation it was verified as. Keeping only the token leaves a claim nobody can retrace when a carrier asks, and political programmes attract exactly that kind of question — usually in the last fortnight before an election.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Campaign Verify token + the filing reference",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Infobip",
        "Campaign Verify",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Infobip",
          "code": "9001",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Obtain the Campaign Verify token, and store the FEC committee ID or the state filing URL beside it in the same record. Done when both can be produced together in a single reply to a carrier query.",
      "notes": "Neither artefact is visible in a registration. What the user has to do is keep them together at the point they are obtained — the filing reference is easy to find while you are applying and surprisingly hard to reconstruct six months later.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Are the Campaign Verify token and the FEC committee ID or state filing URL stored together, retrievable in one reply?",
        "howToCheck": [
          "Record the filing reference beside the token at the moment you obtain it — it is easy to find while applying and surprisingly hard to reconstruct six months later.",
          "Test it: ask whoever answers carrier queries to produce both, now."
        ],
        "failureLooksLike": "Only the token was kept. It proves an organisation was verified and not which one, and the question arrives in the last fortnight before an election."
      }
    },
    {
      "id": "BRD-244",
      "slug": "brd-244",
      "title": "A political token imports only onto an eligible, campaign-free brand",
      "statement": "A Campaign Verify token may be imported only onto a US-based non-profit brand without 501(c) status, and only while the brand has no active campaigns.",
      "rationale": "Two independent conditions, and the second is the one that catches people: the import is refused once campaigns exist, so a brand that registered a campaign first has to unwind it to complete the verification. Political teams reliably register the campaign first, because the campaign is the thing they are actually trying to launch.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Campaign Verify import target",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "541",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "540",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Import the token onto the brand before registering any campaign under it. If campaigns already exist, deactivate them, complete the import, then register them again. Done when the import lands on a US non-profit brand with no 501(c) status and no active campaigns.",
      "example": "entity_type: NON_PROFIT · country: US · tax_exempt_status: (none) · active campaigns: 0 — then import the token",
      "pitfalls": [
        "Deactivating a campaign to unblock the import means re-registering it afterwards, and the second registration is reviewed like any other."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-246",
      "slug": "brd-246",
      "title": "A political token is used once and once only",
      "statement": "Each Campaign Verify token is single-use and unique to one brand and CSP; a second use requires a reissue or a fresh political vet.",
      "rationale": "Standard vetting tokens can be spread across duplicate brand instances (BRD-218) and political ones cannot, so an operator who learned the first behaviour applies it to the second and the reuse is refused. Getting another token is not a self-service action — it means going back to Campaign Verify — which is why this costs days rather than minutes.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Campaign Verify token",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21722",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "543",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use each token on exactly one brand at one CSP. Where the same organisation registers through two providers, request a second token from Campaign Verify rather than importing the first one twice — and request it before you need it.",
      "notes": "Absorbs OPS-320, which contrasts single-use political tokens with the multi-use standard tokens in BRD-218. Whether a token has already been consumed is registry state we cannot see. What the user has to track is which brand each token went to, because the refusal on the second import does not say where the first one was used.",
      "catalogIds": [
        "OPS-320"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has this Campaign Verify token been used on any other brand or at any other CSP?",
        "howToCheck": [
          "Keep a record of which brand each token went to. The refusal on a second import does not say where the first one was used.",
          "Registering the same organisation through two providers means requesting a second token from Campaign Verify — before you need it, since it is not self-service."
        ],
        "failureLooksLike": "Standard vetting tokens can be spread across duplicate brand instances, so an operator applies the same habit to a political one. The reuse is refused and getting another costs days."
      }
    },
    {
      "id": "BRD-247",
      "slug": "brd-247",
      "title": "Check the token has not expired before importing it",
      "statement": "A Campaign Verify token must be checked for expiry before it is imported.",
      "rationale": "Tokens are obtained when the paperwork is ready and imported when the campaign is, and those can be months apart — so an expired token is the normal consequence of an organised team working ahead. The import is refused, and the replacement has to come from Campaign Verify rather than from the provider.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the token expiry",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21722",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30713",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Read the expiry date off the token before importing, and request a fresh one if it has passed or is close. Done when the token in hand is current at the moment of import, not at the moment it was issued.",
      "example": "token expiry: 2026-11-30, imported 2026-08-14 — current at the time of import",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-248",
      "slug": "brd-248",
      "title": "One political import at a time",
      "statement": "A second Campaign Verify import must not be submitted while one is in progress.",
      "rationale": "The second request is refused rather than queued, and because a political import is slow it looks stalled long before it is — so resubmitting is the obvious thing to do and it makes the situation worse rather than better. On a dated campaign, the lost cycle is the actual cost.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the Campaign Verify import queue",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "21723",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Let the in-flight import finish before submitting anything else against this brand. If it has genuinely stalled, ask the provider to check its state rather than resubmitting — a second request is refused and does not replace the first.",
      "example": "One political import in flight — check its state rather than submitting a second",
      "notes": "Absorbs OPS-321, which states the same in-flight restriction and the request fields it applies to.",
      "catalogIds": [
        "OPS-321"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-249",
      "slug": "brd-249",
      "title": "The political filing email must match the registration",
      "statement": "For an Aegis political vet, the filing email must exactly match the email on the political registration, and must not be a consumer domain.",
      "rationale": "The vet confirms that the person filing is the one named in the public political registration, and the email is the identifier it compares — so a campaign manager who files with their own address fails a check about somebody else's identity. Consumer domains are refused outright, which catches small local campaigns that genuinely run from a personal mailbox.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the political filing email",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Aegis"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Use the exact email address on the FEC or state filing. If that filing carries a consumer address, update the filing first — the vet compares against the register, so the register is what has to change.",
      "example": "filing email: press@riversideshelter.org, matching the address on the committee filing — not a personal gmail address",
      "notes": "Whether the address matches the political register needs the register itself, which we do not hold. What the judgement can see is whether a consumer domain was used, which is the half that is refused outright. The user has to compare the address against their own filing before ordering the vet, because the fee is spent on the mismatch.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is the filing email on the vet order character-identical to the email on the FEC or state political registration, and is it not a consumer domain?",
        "howToCheck": [
          "Open your own political filing and copy the address from it.",
          "Where the filing itself carries a personal address, update the filing first — the vet compares against the register, so the register is what has to change."
        ],
        "failureLooksLike": "A campaign manager files with their own address. The vet is checking somebody else's identity, the fee is spent on the mismatch, and small local campaigns that genuinely run from a personal mailbox are refused outright."
      }
    },
    {
      "id": "BRD-250",
      "slug": "brd-250",
      "title": "The political vetting contact must be named in the registration",
      "statement": "The contact name on an Aegis political vet must match an authorised name in the political registration — not the CSP's or the aggregator's.",
      "rationale": "The vet verifies a named individual against the public filing, so the CSP contact who is doing the paperwork is exactly the wrong name to use. It is the natural one to use, because they are the person completing the form, and the failure comes back as an identity mismatch that reads as though the organisation itself was not recognised.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the political vetting contact name",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Aegis"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Take the contact name from the political filing — the treasurer or the authorised representative named there — and use it on the vet, even if that person is not the one submitting. Confirm they are available to receive the couriered PIN.",
      "notes": "The authorised names are in the public filing rather than in the registration. What the user has to do is open their own FEC or state filing and copy the name from it, and warn that person that a courier is coming.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the contact name on the vet an authorised name from the political filing, and does that person know a courier is coming?",
        "howToCheck": [
          "Open the FEC or state filing and copy the treasurer's or authorised representative's name from it — not the name of whoever is completing the paperwork.",
          "Warn that person that a couriered PIN is on its way and confirm they will be there to receive it."
        ],
        "failureLooksLike": "The CSP contact doing the form uses their own name. The failure returns as an identity mismatch that reads as though the organisation itself was not recognised."
      }
    },
    {
      "id": "BRD-251",
      "slug": "brd-251",
      "title": "A federal donation programme needs a current FEC registration",
      "statement": "A federal candidate, committee or party running a donation programme must be currently registered with the FEC.",
      "rationale": "Political donation messaging carries the highest scrutiny of any use case, and a current FEC registration is the baseline that makes the rest of it reviewable. \"Currently\" is the load-bearing word: registrations lapse between cycles, and a committee reactivating for a new cycle can be messaging weeks before its filing catches up.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the FEC registration",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Confirm the committee's FEC registration is current before any donation messaging goes out, and keep the committee ID with the brand record. State and local candidates use the equivalent state filing.",
      "notes": "FEC status is public but not something we query. What the user has to do is check their own committee in the FEC database and confirm it is active for the current cycle — a lapsed registration is the common case at the start of a cycle, and it is fixed by filing rather than by anything in the messaging setup.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the committee's FEC registration active for the current cycle, today?",
        "howToCheck": [
          "Look the committee up in the FEC database and confirm the registration is current, not merely present. State and local candidates check the equivalent state filing.",
          "Keep the committee ID with the brand record."
        ],
        "failureLooksLike": "A committee reactivating for a new cycle is messaging weeks before its filing catches up. \"Currently registered\" is the load-bearing phrase, and it is fixed by filing rather than by anything in the messaging setup."
      }
    },
    {
      "id": "BRD-254",
      "slug": "brd-254",
      "title": "Appeal only a vet that has finished",
      "statement": "An appeal may be filed only against a vet in a final state — never against one still pending.",
      "rationale": "A pending vet has not decided anything, so an appeal against it is refused on entry and the attempt is wasted. The reason people file early is that a vet in progress is indistinguishable from one that is stuck, and the appeal form is available the whole time.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the vet state",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "592",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Wait for the vet to reach ACTIVE, COMPLETE, FAILED or EXPIRED before filing. If it looks stuck, the cause is usually an unanswered 2FA rather than a queue — chase that instead. Done when the appeal is filed against a vet that has actually concluded.",
      "example": "appeal filed against a vet in FAILED state — not against one still PENDING",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-255",
      "slug": "brd-255",
      "title": "Choose an appeal category valid for this entity type",
      "statement": "At least one appeal category must be selected, and it must be one that is valid for the brand's entity type.",
      "rationale": "Categories route the appeal to the reviewer who can decide it, so one that does not apply to this entity type has nowhere to go and the filing is rejected rather than reassigned. The list is offered in full regardless of entity type, which is precisely why the wrong one gets chosen.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "appeal.appeal_categories",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "559",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the category that names what you are contesting, and check it applies to your entity type — tax-exempt status only for a non-profit, government status only for a government body. Done when at least one valid category is selected.",
      "example": "appeal_categories: [\"TAX_EXEMPT_STATUS\"] on a NON_PROFIT brand",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-256",
      "slug": "brd-256",
      "title": "The low-score category does not apply to an identity appeal",
      "statement": "The LOW_SCORE appeal category is valid only for an external vetting appeal, never for an identity-status appeal.",
      "rationale": "Identity and score are decided by different processes, so an identity appeal filed under LOW_SCORE reaches a reviewer who cannot act on it and is refused. It is an easy mistake because a failed identity check and a poor score both present to the user as \"the brand did not pass\", and LOW_SCORE is the category that sounds like it covers both.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "appeal.appeal_categories",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "559",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "For an identity appeal, choose the category naming the field in dispute — business name, address or contact. Keep LOW_SCORE for appealing the result of an external vet. Done when the category matches which of the two decisions is being contested.",
      "example": "appeal_categories: [\"BUSINESS_NAME\"] on an identity appeal — LOW_SCORE only on a vetting appeal",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-257",
      "slug": "brd-257",
      "title": "Keep the appeal explanation inside its limit",
      "statement": "The appeal explanation must be no longer than 1,024 characters.",
      "rationale": "The field is silently bounded, and an appeal is exactly the moment someone writes at length — the whole history of the registration, every correction attempted. The submission is rejected on the length rather than truncated, so the work is lost along with the attempt.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "appeal.explanation",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the explanation as three sentences: what was decided, what is wrong with it, and what the attached document proves. Put the detail in the attachments, which is where a reviewer looks anyway. Done when the field is under 1,024 characters.",
      "example": "explanation: \"The brand was refused as unverified. The legal name on the registration matches the attached CP-575 exactly, including the comma before LLC. The address matches the same letter. Please re-run verification against the attached document.\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-258",
      "slug": "brd-258",
      "title": "Respect the appeal attachment limits",
      "statement": "An appeal may carry at most ten attachments, each no larger than 10 MB, and no more than 30 MB in total.",
      "rationale": "Three separate limits apply at once and only the one you hit is reported, so an appeal built from phone photographs of documents fails on total size after being trimmed to ten files. Every attempt costs a cycle, and appeals are usually filed under time pressure with a campaign already blocked.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the appeal attachments",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "544",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Combine the pages of each document into one PDF and compress scans to text-readable rather than photographic quality. Two or three well-made PDFs beat ten photographs and land inside every limit at once. Done when there are ten or fewer files, none over 10 MB, and 30 MB in total.",
      "example": "attachments: cp575.pdf (1.2 MB), state-registration.pdf (0.8 MB) — two documents, all pages in each",
      "notes": "Absorbs OPS-328, which states the same attachment limits alongside the explanation length in BRD-257. Combining pages into one PDF also discharges BRD-260, which requires every page of a multi-page document to be attached.",
      "catalogIds": [
        "OPS-328"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-260",
      "slug": "brd-260",
      "title": "Attach every page of a multi-page document",
      "statement": "Where a supporting document runs to several pages, all of them must be attached — partial submissions are rejected.",
      "rationale": "People attach the page with the answer on it, because that is the page that matters to them. The reviewer needs the document to be self-evidently the whole document — an EIN letter is three pages and a single page of it could have come from anywhere — so a partial submission is refused without the argument being read.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the appeal attachments",
      "severity": "HIGH",
      "detectability": [
        "HUMAN",
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Scan the document end to end and attach it as one PDF, including the pages that look like boilerplate. Done when the attachment has the same number of pages as the paper document.",
      "notes": "Only the person holding the document knows how many pages it has. What the user must do is check the page count before attaching — and combining the pages into a single PDF also keeps the file count inside the limits in BRD-258.",
      "catalogIds": [
        "OPS-327"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the attachment have the same number of pages as the paper document in your hand?",
        "howToCheck": [
          "Count the pages of the original, then count the pages of the PDF you are attaching.",
          "Scan end to end and combine into a single PDF, boilerplate pages included — that also keeps the file count inside the attachment limits."
        ],
        "failureLooksLike": "The page with the answer on it is attached alone. An EIN letter runs to three pages, and one page of it could have come from anywhere, so the appeal is refused without the argument being read."
      }
    },
    {
      "id": "BRD-261",
      "slug": "brd-261",
      "title": "Only federal tax documentation counts in an appeal",
      "statement": "Appeals must rely on federal-level tax documentation; state-level documents are not accepted.",
      "rationale": "Verification runs against federal records, so a state filing — however official — does not corroborate the thing being checked and the appeal fails with the document attached. State certificates are the ones businesses have to hand, which is why they are the ones that get sent.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the appeal attachments",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio",
        "HighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach the IRS CP-575 or a 147C letter. If neither is to hand, request a 147C from the IRS Business & Specialty Tax Line before filing — it is free and it is the document the appeal actually needs.",
      "notes": "We cannot see which document is attached. What the user has to do is check the letterhead: a state incorporation certificate, a state tax registration or a good-standing certificate will not carry the appeal, and sending one costs a full cycle before anyone says so.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is the document you are attaching federal — a CP-575 or 147C — rather than a state filing?",
        "howToCheck": [
          "Read the letterhead. A state incorporation certificate, a state tax registration or a certificate of good standing will not carry the appeal.",
          "No federal letter to hand: request a 147C from the IRS Business & Specialty Tax Line before filing the appeal. It is free."
        ],
        "failureLooksLike": "The state certificate is the one the business has in a drawer, so it is the one that gets sent. The appeal fails with a perfectly official document attached, and a full cycle is lost before anyone says why."
      }
    },
    {
      "id": "BRD-263",
      "slug": "brd-263",
      "title": "Appeal a missing status attribute even on a verified brand",
      "statement": "A VERIFIED non-profit or government brand must still appeal where its tax-exempt or government attribute is missing.",
      "rationale": "Verification and the status attribute are separate outcomes, and only the second one opens the use cases and the throughput the classification exists for — so a brand can be VERIFIED, look entirely healthy, and still be treated as an ordinary commercial sender. Nobody appeals a brand that passed, which is exactly why this sits unnoticed for months.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the optional attributes + the appeal",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "NON_PROFIT",
          "GOVERNMENT"
        ]
      },
      "applicabilityText": "Applies when the brand is a non-profit and government.",
      "universal": false,
      "remediation": "Check the brand record for the tax-exempt or government attribute once verification completes. If it is missing, file an appeal in that category with the IRS determination letter or the authorising statute attached — do not re-register. Done when the attribute reads back on the brand.",
      "example": "tax_exempt_status: 501(c)(3) present on the brand record after verification — not merely claimed on the form",
      "notes": "Absorbs OPS-326, which states which identity statuses can be appealed and for which entity types. The check reads what the registration declares; whether the attribute was actually SET by the registry is something the user has to read off the brand record after verification, and this rule is the reminder to go and look.",
      "catalogIds": [
        "OPS-326"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-267",
      "slug": "brd-267",
      "title": "Every brand field must be within its maximum length",
      "statement": "Each brand text field must be within the strictest cap its downstream targets enforce.",
      "rationale": "Over-long values are truncated or rejected depending on the provider, and Bandwidth applies the cap at provisioning rather than at vetting — so a brand can verify successfully and then fail when numbers are attached to it, which is the point at which nobody is looking at the brand record any more.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "all brand text fields",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Shorten the named field to its cap. Where a legal name genuinely exceeds 100 characters, use the first line of the IRS-registered name — that is what BRD-007 asks for anyway. Done when every field is inside its limit.",
      "example": "company_name: Acme Coffee Co, LLC — 19 of 100 characters",
      "notes": "Caps are the strictest across the documented targets, which is Telnyx for most fields: displayName, companyName, email, street, city, website ≤100; ein, phone, state ≤20; postalCode, stockSymbol ≤10; country exactly 2. TCR is looser on several, so a value inside these passes everywhere.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-268",
      "slug": "brd-268",
      "title": "Brand contact fields must contain no spaces",
      "statement": "The brand contact email and phone fields must contain no whitespace.",
      "rationale": "A trailing space survives a copy-paste and is invisible in the field, and the registry compares these values as strings — so the address that looks identical to the one on file does not match it. It is the defect users are least able to see, which is why it is worth catching mechanically rather than asking them to look again.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.email + brand.phone",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip every space from the email and phone fields, including leading and trailing ones. Done when neither value contains a space character anywhere.",
      "example": "email: support@acmecoffee.com · phone: +14155550134",
      "pitfalls": [
        "Pasting from a spreadsheet or a PDF is where the trailing space comes from, and the field will look correct in every screenshot you take of it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-269",
      "slug": "brd-269",
      "title": "No emoji in any brand field",
      "statement": "No brand field may contain an emoji or pictograph.",
      "rationale": "Brand fields are matched against tax and registry records, which hold plain text — an emoji guarantees the match fails, and several providers reject the payload outright. It arrives through display names copied from a social profile, where the emoji is part of how the brand presents itself.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "all brand text fields",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the emoji from the named field. Keep it in your social profiles and your message content where it is fine — it is only the registry fields that must be plain. Done when every brand field is plain business text.",
      "example": "display_name: Acme Coffee — not \"Acme Coffee ☕\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-270",
      "slug": "brd-270",
      "title": "Placeholder values are not answers",
      "statement": "Required brand contact and address fields must not hold placeholder values such as N/A, TBD or none.",
      "rationale": "A form that will not submit without a value teaches people to type N/A, and the field is then populated and useless — worse than empty, because nothing downstream flags it as missing. Reviewers read it as a business unwilling to give its own address, which is a much less charitable reading than the one that is true.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand contact and address fields",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the placeholder with the real value. Where you genuinely do not have one — a business with no suite number, say — leave the field empty rather than filling it with a word. Done when every populated field holds real data.",
      "example": "street: 1240 Mission St, Suite 400 — not \"N/A\" and not \"TBD\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-271",
      "slug": "brd-271",
      "title": "Brand content must be submitted in English",
      "statement": "Every brand text field must be submitted in English; non-English submissions are rejected as untranslatable.",
      "rationale": "Reviewers work in English and the registry offers no translation step, so a record in another script is not read strictly — it is returned. Businesses that trade bilingually submit their own language honestly, and the rejection reason (\"untranslatable\") gives no hint that the fix is to transliterate rather than to correct anything. The messages themselves may be in any language; it is only the registration that has to be readable.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "all brand text fields",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Infobip"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Re-enter the named fields in English, transliterating the legal name into the Latin alphabet exactly as it appears on the English-language registration document. Done when every brand field is in Latin script and a reviewer with no other language can read the record.",
      "example": "company_name: Acme Coffee Co, LLC · city: San Francisco — Latin script throughout, whatever language the messages themselves use",
      "pitfalls": [
        "Accented Latin characters are fine — José, Zürich, Ñ. It is a change of script, not of alphabet, that stops the record being readable."
      ],
      "notes": "Only the script half is mechanical, and that is all this check claims: it reports Cyrillic, Greek, Hebrew, Arabic, Devanagari, Thai, CJK and Hangul in a brand field. Whether Latin-script text is actually English — French, Spanish, German — is a judgement no predicate can make, and the rule deliberately does not pretend to. Emoji are BRD-269 and are excluded here so one defect is not reported twice.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-272",
      "slug": "brd-272",
      "title": "The end-business block must be complete, not partly filled",
      "statement": "On a toll-free verification, every end-business detail must be populated rather than a subset of them.",
      "rationale": "A partly filled end-business block is rejected as a whole rather than field by field, and the reviewer cannot tell an omission from a business that has no answer. It arrives from integrations that map whichever fields their own record happens to hold, so the gaps are wherever the source system was thin rather than wherever the business is.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the end-business block",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "TFV 1105",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fill every end-business field before submitting: legal name, address, contact name, phone, email and website. Where a value is genuinely unknown, get it from the customer rather than leaving the field out — a partial submission costs a full review cycle.",
      "example": "company_name, street, city, state, postal_code, country, rep first/last name, phone, email and website — all populated",
      "notes": "Scoped to the toll-free path, where the end-business block is submitted as a unit and rejected as one. The equivalent completeness requirement for a 10DLC brand is spread across BRD-001, BRD-017, BRD-091 and BRD-102, each of which names its own field — so this rule does not repeat them.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-273",
      "slug": "brd-273",
      "title": "Contact, email, address and URL must each stand up on their own",
      "statement": "The representative, the email, the address and the website must each independently resolve or validate.",
      "rationale": "Verification does not average across the record: each identity component is checked separately, and one that cannot be resolved drags the whole submission rather than being outweighed by the four that could. Registrations assembled from several internal systems are where this bites, because each field is correct in its own system and one of them is stale.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.authorized_rep + brand.email + address + brand.website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "EXTERNAL_DATA"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check each component against its own source before submitting: the representative against the company's own staff list, the email by sending a test, the address against the postal authority, the website by opening it in a private window. Done when all four resolve independently.",
      "example": "rep: Jane Doe, CEO · email: jane.doe@acmecoffee.com · address: 1240 Mission St, Suite 400, San Francisco, CA 94103 · website: https://acmecoffee.com",
      "notes": "The crawl settles the website and, where the site publishes them, the address and the contact details. Whether the mailbox accepts mail and whether the address is deliverable are BRD-115 and BRD-095 and are external. What the user has to do is the four-step check in the remediation, because a component that fails here fails silently as a lowered score rather than as a named finding.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you checked all four identity components against their own sources — representative, email, address, website — rather than assuming the record is right because most of it is?",
        "howToCheck": [
          "Representative: against the company's own staff list.",
          "Email: by sending a test message from outside.",
          "Address: against the postal authority's standardised form.",
          "Website: by opening it in a private window, logged out."
        ],
        "failureLooksLike": "A registration assembled from four internal systems, each correct in its own system and one of them stale. Verification does not average — the stale one drags the whole submission, silently, as a lowered score."
      }
    },
    {
      "id": "BRD-274",
      "slug": "brd-274",
      "title": "RCS brand assets have exact dimension, size and format constraints",
      "statement": "An RCS logo must be 224×224 pixels and at most 50 KB, a banner 1440×448 and at most 200 KB, both JPEG or PNG, with at most 50 of each per brand.",
      "rationale": "The assets render inside the verified sender card on a phone, so the constraints are exact rather than advisory and the upload is refused rather than resized. Design teams export at the resolution their brand guidelines specify, which is invariably larger, and the rejection arrives as a numeric code after the artwork has been signed off.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.assets",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "546",
          "remediable": true
        },
        {
          "provider": "TCR",
          "code": "547",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Export the logo at exactly 224×224 and the banner at exactly 1440×448, as PNG or JPEG, and compress each under its ceiling — 50 KB for the logo, 200 KB for the banner. Done when every asset matches its exact dimensions and sits under its size limit.",
      "example": "logo: acme-logo-224.png, 224×224, 38 KB · banner: acme-banner-1440.png, 1440×448, 164 KB",
      "pitfalls": [
        "The size ceilings are tight for photographic artwork. A flat logo compresses well under 50 KB; a photograph at the same dimensions will not."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-275",
      "slug": "brd-275",
      "title": "Brands that buy, sell or share consumer data are not carried",
      "statement": "A brand whose business is buying, selling or sharing consumer information with third parties must not be registered for A2P messaging.",
      "rationale": "The channel rests on consent running from one consumer to one named sender, and a business whose product is the onward movement of consumer data breaks that link by design — whoever eventually messages the consumer is not who they agreed to hear from. It is refused at the brand rather than at the campaign because no campaign copy can repair a consent chain the business model is built to break.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth",
        "AWS",
        "Bird",
        "Infobip",
        "Sinch",
        "Plivo",
        "Commio"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7108",
          "remediable": false
        },
        {
          "provider": "Sinch",
          "code": "CR2012",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A lead-generation or data-broker business cannot be registered for A2P messaging. Where the business also has a first-party relationship — its own customers, its own service — register that entity and that programme separately, with consent collected directly and no onward sharing.",
      "notes": "The catalog is explicit that this is a brand-eligibility verdict rather than a site-content finding: the fix is a different channel, not a different page. Its campaign-content twin is CMP-AFFILIATE, which refuses affiliate marketing on the same reasoning one layer down.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-276",
      "slug": "brd-276",
      "title": "Gambling, betting and lottery brands are not carried",
      "statement": "A brand whose business is gambling, betting, casino, lottery or raffle must not be registered for A2P messaging.",
      "rationale": "Carriers exclude the whole category rather than policing it message by message, because gambling messaging reaches minors and problem gamblers through exactly the channel that is hardest to age-gate. The trap is that TCR publishes a GAMBLING vertical, so the registration form appears to invite the business it will then refuse.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Bird",
        "Infobip",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "704",
          "remediable": false
        },
        {
          "provider": "Sinch",
          "code": "CR2013",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A gambling business cannot be registered for A2P messaging. Selecting the GAMBLING vertical does not open the category; it identifies the brand as ineligible. Use email or an app channel for these programmes.",
      "notes": "Note the vertical trap: TCR has a GAMBLING vertical and choosing it does not confer eligibility. The campaign-content twin is the SHAFT gambling prohibition in `message-shaft.ts`.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-277",
      "slug": "brd-277",
      "title": "High-risk financial services brands are not carried",
      "statement": "A brand whose business is high-risk financial services — payday and short-term lending, indirect loan marketing, stock alerts or crypto must not be registered for A2P messaging.",
      "rationale": "This category is where SMS fraud concentrates, so carriers exclude the business model rather than reading each campaign — the same message from a first-party lender and from a lead-selling loan site is indistinguishable in a text and very different in consequence. The cost of the blanket rule is that legitimate, licensed, first-party lenders get caught by it unless the carve-out is applied deliberately.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Bird",
        "Infobip",
        "Sinch",
        "Plivo",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "709",
          "remediable": false
        },
        {
          "provider": "Sinch",
          "code": "CR2014",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the business is a licensed first-party lender servicing its own loans, say so explicitly in the brand description and the campaign — the carve-out is real and it turns on first-party servicing. Where the business markets credit it does not originate, or promotes securities or crypto, it cannot be registered.",
      "notes": "The first-party carve-out is load-bearing and is the same one MSG-HIGH-RISK-FINANCIAL carries at the campaign layer; fixture 5 of the golden corpus exists to keep it from being dropped. Sinch permits 2FA-only traffic for stocks, investing and crypto businesses, which is why that carve-out is stated separately.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-278",
      "slug": "brd-278",
      "title": "Cannabis, CBD and related brands are not carried, whatever the traffic",
      "statement": "A brand whose business is cannabis, CBD, hemp derivatives, kratom or drug paraphernalia must not be registered for A2P messaging.",
      "rationale": "The prohibition attaches to the business rather than to the message, and it holds regardless of state legality — so a licensed dispensary sending nothing but appointment reminders or two-factor codes is still ineligible. That is the part legitimate operators do not believe until it happens: the traffic is harmless, the business is the disqualifier, and logistics companies serving the sector are caught by the same rule.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "CRAWL",
        "EXTERNAL_DATA"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Bird",
        "Sinch",
        "Infobip",
        "Plivo"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "701",
          "remediable": false
        },
        {
          "provider": "Sinch",
          "code": "CR2015",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A cannabis-sector brand cannot be registered for A2P messaging in the US, including for two-factor or transactional traffic. Use email, an app, or a web channel for customer messaging.",
      "notes": "Absorbs MSG-191: the ban attaches to the business type, so it holds even for 2FA-only or purely transactional traffic, which is exactly what MSG-191 states at the message layer. Whether a business is licensed in the cannabis sector may need a state licence register the product does not hold, so a brand that reads as ambiguous should be confirmed by the user before submission rather than after.",
      "catalogIds": [
        "MSG-191"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does this business sell, distribute, or serve the cannabis, CBD, hemp, kratom or paraphernalia sector in any part of its trade?",
        "howToCheck": [
          "Ask what the business actually sells, not what these messages say. The prohibition attaches to the business and holds regardless of state licensing.",
          "Include logistics, payments and services to the sector — they are caught by the same rule.",
          "Where the answer is ambiguous, settle it before submitting rather than after."
        ],
        "failureLooksLike": "A licensed dispensary sending nothing but appointment reminders or two-factor codes is still ineligible. The traffic is harmless and the business is the disqualifier."
      }
    },
    {
      "id": "BRD-279",
      "slug": "brd-279",
      "title": "Third-party job boards are not carried",
      "statement": "A brand whose business is a third-party job board or staffing aggregator must not be registered for A2P messaging.",
      "rationale": "Job messaging is permitted from the employer that is actually hiring and refused from intermediaries, because recruitment texts from a company the recipient never applied to are the classic shape of an employment scam. Genuine job boards are caught by a rule aimed at the fraudulent ones, and the distinction the carriers draw is first-party hiring rather than legitimacy.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Infobip",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7001",
          "remediable": false
        },
        {
          "provider": "Sinch",
          "code": "CR2006",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Job messaging must come from the hiring brand itself. Where you operate a board, the employer registers its own brand and messages its own candidates; where you place workers, message the workers you employ about their own engagements.",
      "notes": "Absorbs WEB-132, which states the same brand-level rejection decided from the site business model rather than from the form.",
      "catalogIds": [
        "WEB-132"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-281",
      "slug": "brd-281",
      "title": "Sweepstakes brands are not carried",
      "statement": "A brand whose business is sweepstakes must not be registered for A2P messaging.",
      "rationale": "Sweepstakes messaging carries both a high complaint rate and real regulatory complexity — the rules differ by state and the prize claim is a natural phishing vector — so the category is excluded at the brand. TCR publishing a SWEEPSTAKE use case makes this genuinely confusing: the use case exists and is gated separately, and selecting it does not make the brand eligible.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A sweepstakes business cannot be registered. Where an ordinary business runs an occasional promotion, keep the brand and the campaign about the business rather than about the draw, and expect the draw itself to need the separately gated use case.",
      "notes": "Decided from the website, which is why this is CRAWL rather than a form judgement — a sweepstakes operator rarely describes itself that way in the brand fields. Note that `use_case=SWEEPSTAKE` exists at TCR and is gated on its own terms; holding it does not make an ineligible brand eligible.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-282",
      "slug": "brd-282",
      "title": "Debt collection, debt relief and credit repair brands are not carried",
      "statement": "A brand whose business is third-party debt collection, debt consolidation or relief, or credit repair must not be registered for A2P messaging.",
      "rationale": "These are refused even with documented first-party consent, which is unusual and worth stating plainly: the category is excluded because the consumers reached are in financial distress and the sector has a long history of deceptive practice, so the consent record does not rescue it. Operators who have carefully collected written agreements from enrolled clients are the ones most surprised.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bird",
        "Infobip",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Sinch",
          "code": "7010",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Third-party debt and credit-repair businesses cannot be registered, and a signed client agreement does not change that. A business chasing its own invoices should say so explicitly in the brand description, because that is first-party collection and is a different thing.",
      "notes": "Refused \"even with first-party consent\" in the catalog, which is why the remediation says so rather than suggesting better consent records. The campaign-content twin is MSG-DEBT, which carries the same first-party carve-out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-283",
      "slug": "brd-283",
      "title": "Get-rich-quick, work-from-home and MLM brands are not carried",
      "statement": "A brand whose business is get-rich-quick, work-from-home, multi-level marketing, mystery shopping or risky investment schemes must not be registered for A2P messaging.",
      "rationale": "These categories are defined by what they promise rather than by what they sell, and the promise — income with little work — is the same one used by outright fraud. Carriers cannot separate the two from a text message, so the category goes. Distributors in a legitimate direct-sales company are caught by this and generally do not know the category includes them.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Vonage",
        "Sinch",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Sinch",
          "code": "7002",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "These business models cannot be registered for A2P messaging. Where a legitimate product business has been mistaken for one, remove income and earnings claims from the brand description and the site — but if the model itself is recruitment-driven, no rewording makes it eligible.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-284",
      "slug": "brd-284",
      "title": "Controlled substance, prescription drug and pyrotechnics brands are not carried",
      "statement": "A brand whose business is controlled substances, prescription drugs, or fireworks and pyrotechnics must not be registered for A2P messaging.",
      "rationale": "Prescription and controlled-substance messaging is excluded because unsolicited drug texts are a recognised harm and the channel cannot verify a prescription; fireworks are excluded on straightforward safety and legality grounds that vary by state. Licensed pharmacies are the case that surprises people, and the boundary is between messaging about a patient's own prescription and promoting the medicine itself.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "brand.vertical + brand fields + website content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Sinch",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Sinch",
          "code": "7005",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A brand whose business is controlled substances, prescription medicines or pyrotechnics cannot be registered. A licensed pharmacy or provider messaging its own patients about their own care should describe the programme in exactly those terms, since that is what the carve-out turns on.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "BRD-285",
      "slug": "brd-285",
      "title": "The brand must not be on the provider's own blocklist",
      "statement": "The brand must not appear on the submitting provider's internal blocklist.",
      "rationale": "Providers keep their own lists of businesses they will not carry, built from their own enforcement history rather than from any published standard — so a brand can satisfy every carrier requirement and still be refused by one provider and accepted by another. The rejection cites the list rather than a reason, which makes it look arbitrary from the outside even when it is not.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the brand identity against the provider blocklist",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Sinch",
          "code": "CR2003",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where a provider refuses a brand on its own blocklist, ask them what triggered it before resubmitting anywhere — a prior enforcement action against a related entity is the usual cause, and it will follow the same details to the next provider. Registering the same business under a different name is a fabricated registration (BRD-286), not a workaround.",
      "notes": "The list is internal to the provider and is not published. What the user has to do is ask: providers will usually say whether the trigger is the business, the EIN, or a person named on the record, and that answer decides whether another provider is worth trying at all.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "If a provider has refused this brand on its own blocklist, have you asked them what triggered it before submitting anywhere else?",
        "howToCheck": [
          "Ask the provider directly. They will usually say whether the trigger is the business, the EIN, or a person named on the record.",
          "That answer decides whether another provider is worth trying at all — a prior enforcement action against a related entity follows the same details.",
          "Re-registering the same business under a different name is a fabricated registration, not a workaround."
        ],
        "failureLooksLike": "The rejection cites the list rather than a reason, so it looks arbitrary. The same details are submitted to a second provider and refused for the same invisible history."
      }
    },
    {
      "id": "BRD-286",
      "slug": "brd-286",
      "title": "Fabricated business details are a permanent disqualification",
      "statement": "The brand record must describe a real business; fabricated details or a known scam pattern must never be submitted.",
      "rationale": "This is the one brand finding with no remediation: a registration judged fabricated is not corrected, it is refused, and the account behind it is reviewed. The reason is that a fake brand is the entire mechanism of an SMS scam — the identity is what a consumer would use to check who texted them, so inventing it defeats the framework rather than bending it. It matters here because well-meaning placeholder data on a demo brand is indistinguishable from the real thing.",
      "layer": "BRAND",
      "layerSlug": "brand",
      "object": "the whole brand record",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "EXTERNAL_DATA"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30959",
          "remediable": false
        },
        {
          "provider": "Twilio",
          "code": "30885",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not submit this registration. Every field must describe a business that exists, at an address it occupies, with a website it controls. If you are testing, use a mock brand (BRD-204), which burns no fee and pollutes no duplicate-detection index.",
      "notes": "Absorbs WEB-144, which is the same prohibition detected from the other side — site details that do not match public records. Twilio treats it as ineligible for resubmission, which is why this is HARD_STOP rather than a retry. The user cannot fix a fabricated record by editing it; what they have to do is register the real business, and if they believe the finding is wrong, appeal with documentation rather than resubmitting.",
      "catalogIds": [
        "WEB-144"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does every field on this brand describe a business that actually exists, at an address it actually occupies, with a website it actually controls?",
        "howToCheck": [
          "Go through the record field by field and name the source of each value: the IRS letter, the lease, the DNS registrar.",
          "Any value that came from a template, a placeholder, or 'we will fix it later' must be replaced before submission, not after.",
          "Testing rather than registering? Use a mock brand — it burns no fee and pollutes no duplicate index."
        ],
        "failureLooksLike": "A demo brand with plausible filler details submitted for real. It is indistinguishable from a fraudulent registration, and the outcome is refusal without resubmission plus a review of the account behind it."
      }
    },
    {
      "id": "CMP-003",
      "slug": "cmp-003",
      "title": "Message flow must not begin or end with whitespace",
      "statement": "The opt-in / message-flow field must carry no leading or trailing whitespace.",
      "rationale": "A newline pasted in front of the text, or a run of spaces left after it, is invisible in the textarea it was typed into — and the validator rejects the field rather than trimming it, naming a field the submitter can see nothing wrong with. It is almost always an artifact of copying the flow out of a document, so the author has no memory of doing anything that would explain the error.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip the blank first line and any trailing spaces or newline before submitting. Done when the value starts on the first character of the first word and stops on the final full stop — paste it into a plain-text editor if you cannot see the difference.",
      "example": "message_flow begins at \"Customers opt in at https://acmecoffee.com/checkout…\" and ends at \"…Screenshot: https://cdn.acmecoffee.com/compliance/optin.png\" — nothing before the C, nothing after the g.",
      "notes": "Catalog CMP-003 records this as AWS form validation only. Kept universal rather than provider-tagged because trimming a field costs nothing anywhere else, and the strict-superset model prefers a free precaution to a provider-conditional rule that silently stops firing when the submission target is unknown.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-010",
      "slug": "cmp-010",
      "title": "HELP and STOP replies should fit in a single 160-character segment",
      "statement": "The HELP and opt-out reply bodies should each fit within 160 characters.",
      "rationale": "A HELP or STOP reply over 160 characters is delivered as a multi-part message, and the parts can arrive out of order or be dropped independently — so the recipient asking for help receives half an answer, or an opt-out confirmation that reads as a fresh marketing message. The 320-character general bound tempts authors into two segments for replies that gain nothing from the extra room.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.help_message + campaign.optout_message",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cut the reply to one segment by dropping the marketing sentence rather than the disclosures — the brand name, the contact route and the STOP instruction all have to stay. Done when both replies are at or under 160 characters.",
      "example": "help_message: \"Acme Coffee Rewards: for help email support@acmecoffee.com or call 415-555-0134. Reply STOP to unsubscribe.\" (110 characters)",
      "notes": "AWS's registration form enforces 160 on these two bodies, tighter than the 320 that CMP-AUTOREPLY-LEN applies from Twilio and Bandwidth. Graded MEDIUM and worded as a fit-in-one-segment rule because the 320 bound is what most providers reject on; this one catches the message that will be split in delivery.",
      "catalogIds": [
        "MSG-013",
        "MSG-016"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-014",
      "slug": "cmp-014",
      "title": "Keyword entries must carry no surrounding whitespace",
      "statement": "Keyword lists must be comma-separated with no space around the individual entries.",
      "rationale": "Keyword lists are stored as one comma-delimited string and split back apart without trimming, so a space typed after a comma becomes part of the next keyword — and \" STOP\" is not STOP. The consumer texts the word on your signage, the match fails, and the opt-out that everyone believes is configured never fires.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optin_keywords + optout_keywords + help_keywords",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Plivo",
        "Bandwidth",
        "TCR",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Retype the lists with commas and no spaces, or trim each entry if you are building the string in code. Done when no keyword begins or ends with a space — which you can only see by selecting the text, so check it in code rather than by eye.",
      "example": "optout_keywords: STOP,QUIT,END,REVOKE,OPTOUT,CANCEL,UNSUBSCRIBE",
      "notes": "A space INSIDE a keyword is a different defect with a different fix and belongs to CMP-KEYWORD-FMT, which rejects the whole token as non-alphanumeric. This rule is only about padding introduced by splitting the delimited string, so the two do not report the same list twice.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-016",
      "slug": "cmp-016",
      "title": "Contact email and phone must contain no spaces",
      "statement": "The registration contact email and phone number must not contain spaces anywhere.",
      "rationale": "A space inside a contact address is the single most common field-hygiene rejection, and it survives because the space is usually trailing — copied along with the address out of a spreadsheet cell or an email signature. The address looks right in the form and is unusable to anything that parses it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign contact email + phone (the brand contact, where the CSP defaults it)",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "6102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Retype the contact email and phone rather than pasting them, or trim the value in a plain-text editor first. Done when neither field contains a space at the start, the end, or in the middle.",
      "example": "contact email: support@acmecoffee.com · contact phone: 415-555-0134",
      "notes": "Bandwidth 6102 verbatim: \"The email and/or phone number must not contain any spaces.\" The catalog states this row more broadly as \"spaces or special characters\"; that wording traces to a secondary reseller paraphrase of the same code, so only the spaces half is asserted here. Separators inside a phone number are governed by the E.164 formatting rule on the brand, not by this one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-017",
      "slug": "cmp-017",
      "title": "Every mandatory campaign attribute must be explicitly set",
      "statement": "The mandatory campaign boolean attributes — embeddedLink, embeddedPhone, ageGated, directLending and numberPool — must each be explicitly true or false, never left unset.",
      "rationale": "An unset boolean is not the same as false: providers reject nulls on required attributes rather than defaulting them, because the declaration is the thing being relied on downstream. It is also the signature failure of a form filled by an integration that only sends changed fields, so the brand sees a rejection for a field they never knowingly left blank.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign attributes",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set every attribute explicitly to true or false rather than leaving it unset — including the ones that are false. Done when your submission payload carries all five keys with boolean values, not when the form merely looks complete.",
      "example": "embeddedLink: false · embeddedPhone: false · ageGated: false · directLending: false · numberPool: false",
      "pitfalls": [
        "A PATCH-style integration that sends only changed fields will drop every attribute the user did not touch, which is why this fails most often on resubmission rather than first submission."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-018",
      "slug": "cmp-018",
      "title": "Attributes that do not apply to the registration type must be empty",
      "statement": "Conditionally-allowed fields must not be populated when they do not apply to the chosen registration type.",
      "rationale": "Populating a field the registration type does not have produces a contradictory record, and validation rejects contradictions rather than ignoring surplus data. It typically happens when a form is cloned from a different brand tier.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign attributes",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Clear attributes that do not apply to this registration type before submitting — notably number pooling on a sole proprietor campaign, which is limited to a single number.",
      "example": "Sole proprietor campaign: number pool false, single number only.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-019",
      "slug": "cmp-019",
      "title": "Policy link fields must be 255 characters or fewer",
      "statement": "The privacy policy and terms links must each be at most 255 characters.",
      "rationale": "A link that overruns the field is stored truncated, so the reviewer follows a URL that 404s and reads it as a policy page that does not exist. The values that overrun are almost always tracking-parameter tails on an otherwise short URL, which carry nothing a reviewer needs.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.privacy_url + campaign.terms_url",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the bare page and drop everything from the \"?\" onward — campaign tags and session parameters serve no purpose here. Done when both links open the right page from a private browsing window and each is comfortably under 255 characters.",
      "example": "https://acmecoffee.com/privacy — not https://acmecoffee.com/privacy?utm_source=tcr&utm_campaign=…",
      "notes": "TCR's own schema allows 2048; the Bandwidth CSP API caps at 255, and 255 is the portable value. Measures whichever of the two links is longer, so one finding names the field that will actually overrun.",
      "catalogIds": [
        "CMP-020",
        "POL-004"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-021",
      "slug": "cmp-021",
      "title": "Reference id must be 50 characters or fewer",
      "statement": "The campaign reference id must be at most 50 characters, and unique within the CSP.",
      "rationale": "The reference id is your own key for finding this campaign again when a rejection arrives months later, and an over-long value is rejected at submission rather than truncated. Teams reach the cap by concatenating a customer name, a use case and a timestamp into one string, which is also what makes it collide with a sibling campaign.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.reference_id",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Bandwidth",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a short opaque key you can look up on your side rather than a descriptive sentence, and confirm no other campaign in your CSP account already uses it. Done when the value is under 50 characters and unique.",
      "example": "reference_id: SP-2026-0142",
      "notes": "Only the length half is settled here. Uniqueness needs the CSP's whole campaign list, which a single registration does not carry — a duplicate surfaces at submission as \"Duplicate campaign\", and the remediation says to check it because we cannot.",
      "catalogIds": [
        "BRD-179"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-023",
      "slug": "cmp-023",
      "title": "Declared embedded links must come with a sample URL",
      "statement": "A campaign declaring embedded links must supply the example link, at most 255 characters and at most fifteen entries.",
      "rationale": "The declared link sample is what a reviewer screens for a shortener, a mismatched domain, or a destination that has nothing to do with the brand — so declaring that your messages carry links and then supplying none leaves the most-screened element of the campaign unreviewable. It fails on resubmission more often than first submission, because the attribute is flipped to true after a rejection and the sample field is not filled in with it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.embedded_link_sample",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Paste a real link your messages will contain into the URL sample field — the live destination, on your own domain, not a placeholder or a shortener. Done when every distinct link domain your campaign sends appears in the list.",
      "example": "embeddedLink: true · embeddedLinkSample: https://acmecoffee.com/sale",
      "notes": "One field, two provider names: TCR takes a single embeddedLinkSample, Vonage takes a URL Samples list of up to fifteen (catalog CMP-171). The obligation is the same in both, so it is one rule reading one input rather than two rules differing only in arity.",
      "catalogIds": [
        "CMP-171"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-024",
      "slug": "cmp-024",
      "title": "Registration fields must be written in English",
      "statement": "Every campaign registration field must be written in English.",
      "rationale": "US reviewers assess in English, and a field they cannot read cannot be checked against any requirement — so a non-English field is not judged leniently, it is rejected. Sending messages in other languages is entirely fine; the registration record is the only thing that must be readable.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.* (all text fields)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the description and message flow in English. Where samples are genuinely sent in another language, keep the original and add an English translation beside it in the same field. Done when every field can be read end to end by an English-only reviewer.",
      "example": "Sample 1: \"Acme Coffee: 20% de descuento este fin de semana. Responde STOP para cancelar.\" / English: \"Acme Coffee: 20% off this weekend. Reply STOP to opt out.\"",
      "pitfalls": [
        "Translating the samples but leaving the message flow in the original language fails the same rule — Bandwidth checks the CTA, flow, and samples separately."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-028",
      "slug": "cmp-028",
      "title": "Opt-in evidence must be a JPG, PNG or PDF under 500 KB",
      "statement": "Uploaded opt-in evidence must be a JPG, PNG or PDF file no larger than 500 KB.",
      "rationale": "The upload field rejects anything else outright, and the two formats people actually reach for are the two that fail: a phone screenshot saved as HEIC, and a full-resolution PNG straight off a retina display that is several megabytes. Both look like ordinary screenshots to the person attaching them, so the rejection reads as arbitrary.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign opt-in evidence upload",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "AWS",
        "TextUs"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Export the screenshot as PNG or JPG — on iOS, change Settings › Camera › Formats to \"Most Compatible\", or open the file and re-save it — and shrink it below 500 KB by cropping to the consent surface rather than the whole page. Done when the file opens in a browser and its size reads under 500 KB.",
      "pitfalls": [
        "Cropping to reduce the size is the right move only until it cuts off the disclosure text; a small screenshot that no longer shows what the consumer agreed to trades this failure for a worse one."
      ],
      "notes": "Verbatim from the AWS campaign registration form: \"The supported file type are PNG, JPEG, and PDF and the maximum file size is 500KB.\" Size is checked only where the registration carries upload metadata; a bare hosted URL gives us the file type from its extension and nothing about its weight, and the check says which of the two it settled rather than implying it checked both.",
      "catalogIds": [
        "CMP-029",
        "CON-022",
        "OPS-330"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-035",
      "slug": "cmp-035",
      "title": "The description must describe this programme, not messaging in general",
      "statement": "The campaign description must describe this specific programme rather than restating that the business sends messages.",
      "rationale": "A description that would fit any business tells a reviewer nothing they can check against the samples, the use case or the opt-in, so there is no way to approve it and the only safe verdict is refusal. It is rarely laziness: the field is presented as an administrative box rather than as the case for the campaign, so people answer the question they think was asked — \"do you send texts?\" — instead of the one that was.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Plivo",
        "Vonage",
        "Sinch",
        "HighLevel"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "2002",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "2001",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the description naming your business, the people who receive the messages, what those messages contain, and where those people opted in. Done when swapping in a competitor's name would make the sentence false.",
      "example": "Acme Coffee sends weekly promotional offers, seasonal sale notifications and rewards balance updates to customers who ticked the marketing consent box at checkout on acmecoffee.com.",
      "pitfalls": [
        "Padding a generic description to clear the 40-character floor makes it longer without making it specific, and length is not what is being graded."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-037",
      "slug": "cmp-037",
      "title": "The description must name the business that sends the messages",
      "statement": "The campaign description must name the business whose messages these are, not the platform or agency registering on its behalf.",
      "rationale": "The consumer who receives the message consented to hear from a particular business, and the vetting is meant to establish that this business is real and reachable. A description written around the platform vets the wrong company entirely, so the review proves nothing about the sender — and it happens by default, because the platform is the one filling in the form and its own name is the one it has to hand.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30894",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the description in the end customer's voice: their business name as sender, their customers as recipients, their opt-in surface as the source. Your platform belongs on the CSP record and nowhere in this field. Done when the description names the same business as the brand record.",
      "example": "Riverside Dental texts patients who booked online about upcoming appointments — not \"Northstar sends appointment reminders on behalf of dental practices\".",
      "pitfalls": [
        "\"Sent via Northstar\" reads as a disclosure and is still the platform in the field. Naming the platform anywhere in the description invites the reviewer to vet it instead."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-038",
      "slug": "cmp-038",
      "title": "Campaign fields must reference one company, not several",
      "statement": "No campaign field may reference a second company, subsidiary, franchise or client brand alongside the registered one.",
      "rationale": "One campaign carries one brand's traffic, and a reviewer reading two company names cannot tell which one holds the consent or which one a complaint would be attributed to. Groups run into this honestly — the parent is the legal entity, the trading name is on the website, and a third name is on the app — so the description names all three in an effort to be complete and reads as pooled traffic.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + campaign.message_flow + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30926",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the entity whose consent record backs this traffic and write every field in that name alone, registering a separate campaign for each other entity. Where the programme genuinely spans franchisees, AGENTS_FRANCHISES is the use case that lets sub-entities be disclosed rather than concealed.",
      "example": "Every field names Acme Coffee — not \"Acme Coffee, part of the Northstar group, on behalf of Acme Roasting Co\".",
      "pitfalls": [
        "The legal suffix and the trading name are one company: \"Acme Coffee Co, LLC\" and \"Acme Coffee\" are not two brands, and neither is a location suffix."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-040",
      "slug": "cmp-040",
      "title": "Promotional content anywhere requires MARKETING to be declared",
      "statement": "Where any campaign text describes promotional or marketing content, MARKETING must be a declared use case or sub-use case.",
      "rationale": "The use case sets the consent standard the campaign is held to, and promotional content needs express written consent where transactional content does not — so an undeclared marketing programme is being judged against a standard that does not apply to it. Businesses reach this by classifying on intent rather than content: a shipping notification carrying a discount code is filed as a delivery notification because that is what it is for, and it is promotional the moment the code is in it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + message_flow + sample[] ↔ campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Infobip",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "3200",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "2200",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "8200",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either declare MARKETING (or MIXED with a marketing sub-use case) and collect promotional-grade consent for everyone on the list, or take the promotional content out of every field until nothing offers, discounts or advertises. Done when the declared use case and the content agree in both directions.",
      "example": "Samples mention \"20% off all espresso blends this weekend\" → use case MARKETING, not DELIVERY_NOTIFICATION.",
      "pitfalls": [
        "Declaring MARKETING is the cheaper half of the fix; the expensive half is that every number on the list now needs express written consent, and re-labelling the campaign does not re-collect it.",
        "One promotional sentence inside an otherwise transactional message makes the whole message promotional. The reviewer grades the message, not the ratio."
      ],
      "notes": "One rule for four catalog rows because the assertion and the fix are identical wherever the promotional language turns up — description (CMP-040), CTA (CMP-071), samples (MSG-150) and the published privacy policy (POL-077). The providers issue a different reject code per field, which is why the catalog keeps them apart; a submitter fixing one has to fix all of them, so splitting the rule would report one defect four times.",
      "catalogIds": [
        "CMP-071",
        "MSG-150",
        "POL-077"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-041",
      "slug": "cmp-041",
      "title": "The description must say how often the messages come",
      "statement": "The campaign description must disclose the messaging frequency.",
      "rationale": "Frequency is what a reviewer compares against the declared volume and against the frequency promised at opt-in, and a description that omits it leaves both comparisons unmade. It is also the disclosure a consumer relies on most directly: the difference between four messages a month and four a week is the difference between a programme they keep and one they report.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "6004",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the frequency claim you make at the opt-in, in the same words. Done when the description, the CTA and the disclosure on your form all say the same thing about how often messages arrive.",
      "example": "Acme Coffee sends up to 4 promotional messages per month to customers who opted in at checkout.",
      "effectiveFrom": "2024-11-06",
      "notes": "Kept separate from CMP-073, which requires the same disclosure in the message-flow field: both fields carry it on the same registration, so satisfying one does not discharge the other. Effective from the Bandwidth vetting change of 2024-11-06.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-042",
      "slug": "cmp-042",
      "title": "A programme that asks for money must say so",
      "statement": "Where the campaign solicits donations, the description must state that donations are collected.",
      "rationale": "Donation solicitation is promotional content whatever the sender's tax status, and it carries fundraising rules of its own — so a charity programme described as \"updates for supporters\" is registered under the wrong consent standard. Non-profits are the ones this catches, and they are the least likely to see it coming, because internally the appeal is not thought of as marketing at all.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Infobip",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "3015",
          "remediable": true
        },
        {
          "provider": "Infobip",
          "code": "9003",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "CHARITY",
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY and POLITICAL.",
      "universal": false,
      "remediation": "Say plainly in the description that the programme asks supporters for donations, and confirm the opt-in disclosure said so too. Done when a reader knows before reading the samples that money will be requested.",
      "example": "Riverside Shelter texts supporters about shelter capacity and urgent supply needs, and asks for donations during winter appeals.",
      "notes": "Tagged to the two use cases whose programmes are fundraising by construction. A commercial programme that also solicits donations is reached by CMP-040 instead, since a donation ask is promotional content.",
      "catalogIds": [
        "MSG-270"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-045",
      "slug": "cmp-045",
      "title": "The programme must be application-to-person, not personal messaging",
      "statement": "A campaign must describe application-to-person business messaging, not person-to-person or influencer-to-follower messaging.",
      "rationale": "10DLC exists for business messaging with consent behind it. A description reading as a person texting their contacts, or a public figure broadcasting to followers, does not fit the framework — and influencer programmes in particular usually have no per-number consent record at all, because the audience was acquired as followers rather than as subscribers.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "applicability": {
        "excludeUseCases": [
          "SOCIAL"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT SOCIAL.",
      "universal": false,
      "remediation": "Describe the programme as business messaging from the registered entity to consumers who opted in with that entity, and say where those consumers opted in. Done when the description names the business as sender and an opt-in surface as the source of the list. If the programme genuinely is a public figure messaging followers, the SOCIAL use case is the route, not a reworded description.",
      "example": "Acme Coffee sends order updates and promotions to customers who opted in at checkout — not \"I text my followers about new drops\".",
      "notes": "Catalog CMP-046 flags the tension with the TCR SOCIAL use case, which Vonage restricts to exactly public figures, platforms and influencers (see CMP-140). Tagged to exclude SOCIAL so the rule does not reject the one use case built for it. SOCIAL is not in USE_CASE_REQUIREMENTS yet, so today the tag documents the carve-out rather than firing.",
      "catalogIds": [
        "CMP-046"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-048",
      "slug": "cmp-048",
      "title": "Emergency framing requires the emergency use case",
      "statement": "A campaign may not describe itself as sending emergency or public-safety alerts unless EMERGENCY is declared and genuinely applies.",
      "rationale": "The emergency use case buys priority treatment and relaxed filtering, so describing ordinary traffic as urgent is an attempt — usually unwitting — to claim deliverability the programme is not entitled to. Businesses reach it through vocabulary rather than intent: an outage notice or a weather closure feels like an emergency, and the word costs the campaign its approval.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description ↔ campaign.usecase",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30929",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the messages in operational terms — \"service interruption notices\", \"closure announcements\" — and reserve emergency language for the declared EMERGENCY use case, which is restricted to genuine public-safety senders. Done when no field claims urgency the use case does not carry.",
      "example": "Acme Coffee sends store closure notices during severe weather — not \"Acme Coffee sends emergency alerts to customers\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-050",
      "slug": "cmp-050",
      "title": "The description must not contain personal data",
      "statement": "The campaign description must not contain individual names, account numbers or other personal data.",
      "rationale": "The description is stored and read across several organisations — the CSP, the registry, the carriers — and none of them is the right custodian for a customer's name or account number. It gets there through a well-meant example: someone illustrates the programme with a real message they sent, and the illustration carries a real person in it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace any real person, account number or order reference with a category or a bracketed placeholder. Done when nothing in the field identifies an individual.",
      "example": "Acme Coffee texts customers their order status — not \"we text customers like Maria Alvarez about order 4471 on card ending 8891\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-051",
      "slug": "cmp-051",
      "title": "A first-party lender must use the phrase the reviewer looks for",
      "statement": "A campaign whose business performs first-party lending must contain the literal phrase \"Direct Lending\" in its description.",
      "rationale": "Lending campaigns are screened against a prohibited category that looks almost identical from the outside — the difference between a licensed lender servicing its own loans and a lead-generation operation is not visible in a sample message. The literal phrase is the marker the screening looks for, so a compliant lender that describes itself in its own words is sorted into the refused pile and given a rejection that reads as a content ban.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30895",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "directLending": true
        }
      },
      "applicabilityText": "Applies when `directLending` is true.",
      "universal": false,
      "remediation": "Put the words \"Direct Lending\" in the description alongside your own account of the programme, and keep the directLending attribute true. Done when the phrase appears verbatim rather than as \"we lend directly\".",
      "example": "Harbor Point Lending is a Direct Lending business texting its own borrowers about payment due dates on loans it originated and services itself.",
      "pitfalls": [
        "The attribute and the phrase are separate requirements. Setting directLending true without the phrase leaves the screening with nothing to match on."
      ],
      "notes": "A Twilio phrasing requirement layered on top of the attribute itself. Not provider-tagged: including the phrase costs a compliant lender nothing anywhere else, and a tag would resolve YES on an unknown submission target regardless, so the tag would buy no protection while spending one of the three the registry allows itself.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-052",
      "slug": "cmp-052",
      "title": "A website that is not live yet must be declared as such",
      "statement": "Where the brand website is not yet published, the description must say so explicitly.",
      "rationale": "A reviewer who opens the submitted website and finds a parked domain or a 404 reads it as a brand that does not exist, which is the single fastest route to refusal. Saying \"the site launches in March; the opt-in form is at this staging URL with a screenshot attached\" turns the same facts into a documented pre-launch registration, and pre-launch registrations are routinely approved.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + brand.website",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30891",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State in the description that the site is not yet live, give the launch timing, and point at the evidence standing in for it. Done when a reviewer who finds nothing at the domain has already been told why.",
      "example": "Acme Coffee launches acmecoffee.com in March 2026. Until then the checkout opt-in runs on the staging site, screenshot attached: https://cdn.acmecoffee.com/compliance/optin.png",
      "catalogIds": [
        "CON-014",
        "WEB-149"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-053",
      "slug": "cmp-053",
      "title": "A site a US reviewer cannot reach must be declared as such",
      "statement": "Where the website is geo-restricted so a US reviewer cannot open it, the description must disclose that and attach a screenshot.",
      "rationale": "A reviewer blocked at the door sees the same thing as a reviewer looking at a dead domain, and treats it the same way. The businesses this hits are the ones doing something reasonable — a regional retailer that serves one country and blocks the rest — so the rejection arrives for a deliberate business decision that nobody thought to mention.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30891",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Say in the description which regions the site serves and that US traffic is blocked, and attach a screenshot of the opt-in page taken from inside the served region. Done when the reviewer has an image of what they cannot load.",
      "example": "The Acme Coffee storefront serves Canada only and blocks US traffic; the checkout opt-in is captured here: https://cdn.acmecoffee.com/compliance/optin-ca.png",
      "catalogIds": [
        "CON-015"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-056",
      "slug": "cmp-056",
      "title": "An ISV registering for a customer must supply the customer's details",
      "statement": "Where an ISV or platform registers on behalf of a customer, the campaign must carry the customer's website and brand information, not the platform's.",
      "rationale": "This is the campaign-side twin of registering the wrong brand. The platform's own site is real and substantial, which makes it a tempting field value — and it means the entity being vetted is not the business whose messages will be sent, so the vetting proves nothing about the sender.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign fields + brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate every campaign field from the end customer: their website, their brand name, their opt-in surface. Your platform belongs on the CSP record only. Done when nothing in the campaign identifies the platform.",
      "example": "Website: acmecoffee.com (the customer) — not northstarplatform.io (the ISV).",
      "notes": "Catalog CMP-056 is conditional on the registration being on behalf of a third party. We hold no \"registered by an ISV\" fact, so the condition lives in the judgement criteria instead of the applicability tag — the rule only fires a finding when the content itself shows a platform.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-057",
      "slug": "cmp-057",
      "title": "A platform registering its own product must say that it is",
      "statement": "Where the registrant is a platform registering a direct offering of its own, the description must state that explicitly.",
      "rationale": "Platforms are screened hard for registering their customers' traffic under their own brand, so a platform that genuinely is the sender looks exactly like the thing reviewers are trained to refuse. Saying so directly is what separates the two, and it is the one thing the description of a legitimate platform programme usually omits — because from the inside there is nothing ambiguous about it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State that the messages are your own product's notifications to your own users, and name the product. Done when the description rules out the reading that you are sending on a customer's behalf.",
      "example": "Northstar texts its own subscribing practices about billing and platform status — these are Northstar's notifications to Northstar customers, not messages sent for a practice.",
      "notes": "The mirror of CMP-056, which catches a platform registering a customer's traffic under its own brand. Conditional on the registrant being a platform at all, and we hold no such fact, so the condition lives in the criteria: the rule opens with a PASS boundary and only speaks when the content itself shows a platform.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-058",
      "slug": "cmp-058",
      "title": "Some programmes are better served by another channel",
      "statement": "A campaign whose traffic does not need 10DLC should be told that toll-free or another channel fits it better.",
      "rationale": "10DLC carries per-campaign vetting, registration fees and throughput tiers that a low-volume one-way notification programme gains nothing from, and a business that registers anyway pays for review cycles it did not need. This is advice rather than a defect, and it is worth giving early: the cost of hearing it after three rejections is much higher than the cost of hearing it now.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + campaign.usecase",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Weigh toll-free verification against 10DLC for a low-volume, one-way, non-marketing programme — it is a single verification rather than a per-campaign one. Done when the channel is a decision you made rather than a default.",
      "example": "A clinic sending a few hundred appointment reminders a month is usually better served by a verified toll-free number than by a registered 10DLC campaign.",
      "pitfalls": [
        "Toll-free is not an escape from a content rejection: the same prohibited categories are refused there, so a campaign turned down for its content will be turned down again."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-059",
      "slug": "cmp-059",
      "title": "Message flow must describe the mechanism end to end",
      "statement": "The message-flow / CTA field must state the exact consent-capture mechanism end to end.",
      "rationale": "Naming a channel is not describing a mechanism. A reviewer needs the whole path — where the consumer is, what they see, what they tick or say, what happens next — because each step is where a compliance defect hides, and a description that skips steps is indistinguishable from one that has steps to hide.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicability": {
        "excludeUseCases": [
          "M2M"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT M2M.",
      "universal": false,
      "remediation": "Write the flow as a single narrative covering place, audience, prompt, action, disclosure wording, and what is stored. Done when the paragraph reads as a walkthrough someone could follow. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Customers opt in at https://acmecoffee.com/checkout, in the block beneath the delivery address fields, after entering delivery details and before placing the order. They see an unchecked box labelled \"Text me offers\" with this disclosure printed beside it: \"By checking this box you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies, up to 4 messages per month. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help.\" Only customers who tick the box are subscribed; they receive order and delivery updates plus weekly promotional offers. Each opt-in is stored with the phone number, timestamp, the disclosure version shown, and the checkout session id. Screenshot: https://cdn.acmecoffee.com/compliance/optin.png",
      "notes": "Catalog CMP-059 is universal except use_case=M2M, which has no consumer to consent. M2M is not in USE_CASE_REQUIREMENTS yet, so today the tag documents the carve-out rather than firing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-060",
      "slug": "cmp-060",
      "title": "Every opt-in method in use must be listed in the message flow",
      "statement": "All opt-in methods actually in use must be described inside the single message-flow field.",
      "rationale": "There is one flow field and it has to cover every collection surface, because an undescribed method is an unaudited one. Brands routinely describe only their web form while also collecting consent verbally at a counter, which leaves the larger half of their list undocumented — and it is the undocumented half that generates the complaints.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List every method you actually use in campaign.message_flow, each in its own paragraph. If you collect consent three ways, the field describes three flows. Done when the methods described match the methods you could be audited on.",
      "example": "Consent is collected three ways: (1) the checkout box at acmecoffee.com/checkout [screenshot attached]; (2) verbally at our counter using the attached script; (3) by texting JOIN to 55512 from our in-store signage [photo attached].",
      "pitfalls": [
        "Dropping a method from the flow does not remove the obligation — it removes the evidence that you meet it."
      ],
      "catalogIds": [
        "CON-003",
        "CON-004"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-061",
      "slug": "cmp-061",
      "title": "Each declared method needs its own complete workflow paragraph",
      "statement": "Every declared opt-in method must carry its own description satisfying that method's own rule set.",
      "rationale": "A compliant web form does not make a verbal opt-in compliant — the two have different required elements, different evidence, and different failure modes. One combined paragraph covering \"various channels\" satisfies neither rule set and is graded against both.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give each method its own paragraph carrying that method's required elements: the web form needs the checkbox and disclosure, the verbal path needs the script elements, the keyword path needs the advertised CTA and confirmation. Done when each paragraph would pass on its own if the others were deleted.",
      "example": "Web: customers tick an unchecked box at acmecoffee.com/checkout reading \"…\". Verbal: agents read the attached script capturing an explicit yes. Keyword: in-store signage reads \"Text JOIN to 55512. Msg frequency varies…\".",
      "notes": "The per-method formulation is marked [INFERRED] in the research — derived from the TCR \"you must list them all\" instruction plus Twilio 30909 rejection causes, not published as a rule in those words. Cite the underlying obligation, not this phrasing, when a user pushes back. Applies only when more than one method is declared; the applicability model cannot express \"more than one\", so the judgement carries that condition instead.",
      "catalogIds": [
        "CON-005"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-062",
      "slug": "cmp-062",
      "title": "Declared opt-in methods must all actually be used",
      "statement": "Opt-in methods selected on the registration must be methods the brand genuinely uses; unused selections must be deselected.",
      "rationale": "Every declared method is a promise a reviewer will check, and each one needs its own evidence. Ticking every box to look thorough multiplies the ways the campaign can fail, because a declared method with no matching evidence is a mismatch rather than a bonus.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign opt-in method declarations",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare only the methods actually in use today. Remove aspirational ones — you can add them later when the evidence exists.",
      "example": "Declared: web form only. Not: web form + keyword + QR + verbal, with one screenshot for all four.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-063",
      "slug": "cmp-063",
      "title": "A method you use and did not declare is the one that will be found",
      "statement": "Every opt-in method actually in use must appear in the submission; none may be left out.",
      "rationale": "The undeclared method is the one generating complaints, because it is the one nobody applied the disclosure checklist to. Businesses omit them without deciding to: the web form is what the registration form asks about, and the fact that the sales team also collects numbers at trade shows never comes up until a recipient asks where the number came from.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "declared opt-in methods vs the flows actually in use",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Ask everyone who could add a number to the list how they do it — sales, support, retail, events, the website — and declare every route that turns up. Done when nobody in the business can name a way onto the list that is not in the submission.",
      "notes": "The mirror of CMP-062, and the half of the pair no payload can settle: a method absent from the submission is also absent from everything we can read. The user has to canvass their own team and add whatever surfaces; we cannot check the answer, so a PASS here means nobody has been asked rather than that nothing was omitted, and the product must say so when it reports one.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has everyone who can add a number to this list been asked how they do it — and does every route they named appear in the submission?",
        "howToCheck": [
          "Canvass sales, support, retail, events and the website. The web form is what the registration form asks about; the trade-show clipboard never comes up.",
          "Add every route that surfaces to the declared methods.",
          "Done when nobody in the business can name a way onto the list that is not in the submission."
        ],
        "failureLooksLike": "The undeclared method is the one generating complaints, because it is the one nobody applied the disclosure checklist to. A pass here means nobody has been asked, not that nothing was omitted."
      }
    },
    {
      "id": "CMP-064",
      "slug": "cmp-064",
      "title": "The opt-in page URL must be a deep link to the consent surface",
      "statement": "The URL in the message flow must point at the page that carries the consent surface, verified against the crawl.",
      "rationale": "A reviewer given a URL expects to land on the consent form. Landing on a homepage, a 404, or a page whose form is elsewhere converts a verifiable opt-in into an unverifiable claim, which is the most common reason an otherwise compliant flow is rejected.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + website",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the exact page holding the consent checkbox, and open it in a private window to confirm it loads for someone with no session.",
      "example": "https://acmecoffee.com/signup — the page with the checkbox, not the homepage.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-065",
      "slug": "cmp-065",
      "title": "A hosted artifact is required when the opt-in is not publicly crawlable",
      "statement": "Where the opt-in surface is login-gated, unpublished, on paper, verbal, or in-store, a publicly hosted screenshot URL is required in the message flow.",
      "rationale": "A reviewer cannot crawl what is not on the public web, so the artifact becomes the only evidence that exists. This is the rule that makes offline collection registrable at all — without it those campaigns simply fail the website-consent checks for reasons that do not apply to them.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr",
          "paper",
          "pos",
          "employer_hr",
          "account_settings"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal, IVR, paper form, point of sale, employer / HR and account settings.",
      "universal": false,
      "remediation": "Host the evidence at a public URL that opens without a login and cite it in the message flow. Confirm it opens in a private browsing window before submitting.",
      "example": "Consent screenshot: https://cdn.acmecoffee.com/compliance/counter-form.pdf",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-067",
      "slug": "cmp-067",
      "title": "The hosted evidence URL must return the artifact, not an error page",
      "statement": "A hosted evidence URL must return 200 and render the artifact rather than a 404, a timeout, a redirect or a placeholder.",
      "rationale": "Evidence links rot between writing the registration and the review, and the failure is silent from the submitter's side because their browser serves the file from cache. A reviewer who follows the link and sees a 404 does not chase it — the campaign is rejected for missing evidence that the submitter can still see on their own screen.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "hosted evidence URL",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30891",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30922",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open every evidence URL in a private browsing window on a device that has never seen it, and replace any that do not render the file itself. Done when each link returns the image or PDF directly rather than a viewer page, a login, or a \"file not found\".",
      "pitfalls": [
        "A cloud-drive preview page returns 200 while showing the reviewer a sign-in prompt, so status code alone is not the test — what came back has to be the artifact."
      ],
      "catalogIds": [
        "WEB-105"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-068",
      "slug": "cmp-068",
      "title": "Evidence links must keep working after approval",
      "statement": "Hosted evidence URLs must stay reachable for the life of the campaign, not only until it is approved.",
      "rationale": "The evidence is what you rely on when a complaint arrives, which is typically long after approval — and by then the share link has expired, the CDN path has been reorganised, or the file was cleaned up as part of a migration. Approval is the point at which everyone stops thinking about these URLs, which is precisely why they rot.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "hosted evidence URL",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Host evidence on a path you control with no expiry, and re-check the links whenever the campaign is renewed or resubmitted. Done when the URLs are somewhere your own retention policy protects rather than in a share link.",
      "pitfalls": [
        "A cloud-drive share link is the common failure: many expire on a schedule, and revoking one person's access can silently break it for everyone."
      ],
      "notes": "Partly off-platform: we can fetch the URLs at scan time, but nobody can verify at registration that they will still resolve in a year. The user has to put the files somewhere durable and re-check them at renewal — the research marks the ongoing-monitoring half as an inference rather than a published rule, so it is surfaced as a warning with the check the user should run themselves, not as a finding against the submission.",
      "catalogIds": [
        "CON-026",
        "WEB-156",
        "OPS-271"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-069",
      "slug": "cmp-069",
      "title": "A URL mentioned in the CTA must also be given as a link field",
      "statement": "Any URL referenced inside the message-flow text must also be supplied in the structured link fields.",
      "rationale": "The structured fields are what gets crawled and screened; a URL that exists only inside prose is read by a human and checked by nothing. Submitters reasonably assume that writing the link once is enough, and the result is a privacy policy that was never fetched, never screened, and never confirmed to exist.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow ↔ campaign.privacy_url + terms_url",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "2120",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Copy each URL out of the flow text into the field built for it — privacy policy, terms, opt-in page — leaving it in the prose as well. Done when every link in the flow also appears in a structured field.",
      "example": "The flow mentions https://acmecoffee.com/privacy → the privacy policy field also carries https://acmecoffee.com/privacy.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-072",
      "slug": "cmp-072",
      "title": "Message flow must state the message types",
      "statement": "The message-flow / CTA field must state the message types the subscriber will receive.",
      "rationale": "Consent is scoped to what was disclosed, so the flow has to record what the consumer was told they would get. A flow that describes the mechanics of ticking a box without saying what arrives afterwards documents an opt-in to nothing in particular, and leaves a reviewer no way to check the samples against the consent.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the categories you actually send — \"order and delivery updates plus weekly promotional offers\" — instead of \"messages\". Done when a reader can predict, from the flow alone, what will land on the subscriber's phone. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Subscribers receive order and delivery updates plus weekly promotional offers from Acme Coffee.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-073",
      "slug": "cmp-073",
      "title": "Message flow must state message frequency",
      "statement": "The message-flow / CTA field must state the message frequency.",
      "rationale": "Frequency is one of the required disclosures at the point of consent, so its absence from the flow text tells a reviewer either that it was never disclosed or that the flow is incomplete. Both readings cost the submission, and the second one costs it twice because the reviewer then doubts the rest of the description.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the frequency claim you make at the opt-in, word for word. Done when the flow and the disclosure on your form say the same thing — a reviewer comparing them will notice if one says \"varies\" and the other promises four a month. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Message frequency varies, up to 4 messages per month.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-074",
      "slug": "cmp-074",
      "title": "Message flow must state that rates may apply",
      "statement": "The message-flow / CTA field must state that message and data rates may apply.",
      "rationale": "The consumer may be billed per message by their own carrier, and this is the disclosure that tells them so before they agree. It is also the most mechanically-checked element in the framework — reviewers look for it in the flow text as well as on the surface — so omitting it is a certain finding in exchange for eight saved words.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Quote the rates sentence inside the disclosure you reproduce in the flow. Done when the phrase \"Message and data rates may apply\" (or a clear equivalent) appears in the flow text itself. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "The disclosure beside the box reads \"Message and data rates may apply.\"",
      "notes": "The catalog exempts Free-To-End-User programmes here (CTIA/T-Mobile, see CMP-152). We hold no FTEU fact, so the applicability model cannot express the carve-out and the rule fires universally; an FTEU programme will see a finding it can dismiss.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-075",
      "slug": "cmp-075",
      "title": "Message flow must state how to get help",
      "statement": "The message-flow / CTA field must state how the consumer gets help.",
      "rationale": "The HELP route is part of the disclosure the consumer sees at opt-in, so it belongs in the record of what they were shown. It also lets a reviewer confirm that the flow, the configured help keywords, and the HELP auto-reply all describe the same programme — three fields that drift apart easily because three different people fill them in.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include the HELP instruction inside the quoted disclosure, and name where HELP lands. Done when the flow names the same keyword you configured on the campaign. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "The disclosure beside the box reads \"Reply HELP for help\"; HELP is answered by our support desk at support@acmecoffee.com.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-076",
      "slug": "cmp-076",
      "title": "Message flow must state how to opt out",
      "statement": "The message-flow / CTA field must state how the consumer opts out.",
      "rationale": "The opt-out instruction is the single most important disclosure a consumer is given, and a flow that omits it is read as evidence that the surface omits it too. Reviewers treat the flow text as the record of what the consumer actually saw, so silence here is not neutral — it is a claim that nothing was said.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include the STOP instruction inside the quoted disclosure. Done when the word STOP appears in the flow text and matches the opt-out keywords declared on the campaign. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "The disclosure beside the box reads \"Reply STOP to unsubscribe\"; STOP is honoured immediately and confirmed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-077",
      "slug": "cmp-077",
      "title": "Message flow must carry the terms",
      "statement": "The message-flow / CTA field must state the programme terms, either in full or as a link.",
      "rationale": "The terms are what the consumer is agreeing to, so a flow that describes an agreement without saying where its content lives records a consent to something unstated. Reviewers treat the flow as the record of what was shown, and a missing terms reference reads as terms that were never presented — which is also usually what happened, because the link sits in the page footer rather than beside the box.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the terms URL inside the disclosure you reproduce in the flow, or write the terms out in full there. Done when the flow text itself contains either the wording or a link a reviewer can open. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "The disclosure beside the box links the programme terms at https://acmecoffee.com/sms-terms.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-078",
      "slug": "cmp-078",
      "title": "Message flow must carry the privacy commitment",
      "statement": "The message-flow / CTA field must state the privacy policy link, or that opt-in data will not be shared.",
      "rationale": "The single commitment carriers care most about is that a mobile number collected for messaging is not passed on, and this is where the consumer is told it. Either form satisfies it — the link or the sentence — which is worth knowing, because a business with no separate privacy page can discharge the requirement with one clause rather than by publishing a document.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the privacy policy URL to the disclosure you reproduce in the flow, or state in the flow that mobile opt-in data is never shared with third parties. Done when one of the two appears in the flow text. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "The disclosure beside the box reads \"We never share your mobile information with third parties\" and links https://acmecoffee.com/privacy.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-079",
      "slug": "cmp-079",
      "title": "Message flow must describe the consumer-facing opt-in action",
      "statement": "The message-flow / CTA field must state how the consumer is told about the programme and what they do to join.",
      "rationale": "The flow must describe the experience, not the plumbing: what the consumer sees, and the specific action they take. This is what lets a reviewer decide the action was affirmative rather than passive — the difference between a box someone ticked and a form someone submitted while consent was assumed.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the control the consumer sees, its label, and the action they take on it. Done when the text names a deliberate act — ticking, texting, signing, saying yes. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Beneath the delivery fields the customer sees an unchecked box labelled \"Text me offers\" with the disclosure printed beside it, and ticks it before placing the order.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-080",
      "slug": "cmp-080",
      "title": "The disclosure must be reproduced in the registration, not only on the site",
      "statement": "The CTA disclosure text must appear inside the registration itself even when it is also published on the website.",
      "rationale": "A reviewer grades the registration record, and a website can change between submission and review — so the copy in the flow field is what the approval is actually granted against. This is the rule behind a surprising share of rejections on sites that are perfectly compliant: everything the reviewer needed was on the page, and none of it was in the form.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Aerialink",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Paste the disclosure the consumer sees into the flow field verbatim, in quotation marks, rather than linking to it. Done when the flow contains the sentence itself and not a pointer to where the sentence lives.",
      "example": "The box is labelled \"Text me offers\" and the disclosure printed beside it reads: \"By checking this box you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help.\"",
      "notes": "Verbatim from the source: \"Even though this information is available on the website, it must be displayed within TCR.\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-082",
      "slug": "cmp-082",
      "title": "Message flow must state where the opt-in occurs",
      "statement": "The message-flow / CTA field must state where on the site, app, or premises the opt-in happens.",
      "rationale": "Reviewers verify by going and looking, so the flow must tell them where to go, described from the consumer's point of view rather than in internal system terms. \"On the checkout page after entering delivery details\" is findable; \"via our CDP integration\" is not, and an unfindable opt-in is treated as an unverifiable one.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the URL or the physical place, plus the position on the page. Done when someone holding only the flow text could navigate to the control and see it. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Opt-in happens on the checkout page at https://acmecoffee.com/checkout, in the block beneath the delivery address fields.",
      "catalogIds": [
        "CON-006"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-084",
      "slug": "cmp-084",
      "title": "Message flow must state who opts in and when",
      "statement": "The message-flow / CTA field must state who is opting in and at what moment.",
      "rationale": "Which customers, at which point in their journey, is what determines whether the consent scope matches the messages sent. A flow that never says who is subscribing cannot be checked against the samples, and it is the field where list-purchase and scraped-number programmes give themselves away.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the audience and the moment: which customers, at which step. Done when the flow makes clear that only people who took the action are on the list. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Customers opt in during checkout, after entering delivery details and before placing the order; only customers who tick the box are subscribed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-085",
      "slug": "cmp-085",
      "title": "Message flow must state how consent is recorded",
      "statement": "The message-flow / CTA field must state how consent is recorded once given.",
      "rationale": "The record is what you rely on when a complaint arrives, and reviewers read its absence as evidence that nothing is retained. Storing only \"opted in: true\" leaves you unable to show WHAT was agreed to, which is the question every TCPA complaint turns on.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the fields you keep: number, timestamp, the disclosure version shown, and the session or agent identity. Done when the flow shows you could reproduce what a given subscriber saw on the day they agreed. Put it in campaign.message_flow itself — this field is graded on its own text, not on what a reviewer would find by opening your site.",
      "example": "Each opt-in is stored with the phone number, timestamp, the disclosure version shown, and the checkout session id.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-088",
      "slug": "cmp-088",
      "title": "The opt-in path should follow a published flow template",
      "statement": "The described opt-in should follow one of the published message-flow templates: digital, verbal, paper, or inbound keyword.",
      "rationale": "Reviewers work from four templates and check the described flow against whichever one it resembles, so a flow that matches none of them is assessed against a standard nobody has written down — which in practice means the strictest reading of all four. Matching a template is not bureaucratic conformity: it is what makes the review predictable, and it usually only requires reordering what is already there.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Restructure the flow to follow the template for your method: digital gives page, control, disclosure, storage; verbal gives context, script, capture, retention; paper gives form, consent language, signature, storage; keyword gives advertisement, keyword, number, confirmation. Done when a reader can name which template the paragraph follows.",
      "example": "Digital template, in order: customers reach https://acmecoffee.com/checkout · tick an unchecked box labelled \"Text me offers\" · read the disclosure printed beside it · the opt-in is stored with number, timestamp and disclosure version.",
      "notes": "Published by one provider as a set of four templates rather than as a rule everyone states. Kept universal and graded as guidance in the criteria: a flow carrying every required element in an unusual order is not a defect, so the FAIL boundary is drawn at a flow that matches no template because elements are missing rather than because they are ordered differently.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-089",
      "slug": "cmp-089",
      "title": "Some opt-in must be described somewhere",
      "statement": "The description or the message flow must describe some form of opt-in, however briefly.",
      "rationale": "This is the floor beneath every other consent rule: a submission that never mentions how anyone joined the list is indistinguishable from one where nobody did. It is a different failure from a thin description — the elements are not merely incomplete, the subject is absent — and it is fixed by writing a sentence rather than by improving one.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "2100",
          "remediable": true
        }
      ],
      "applicability": {
        "excludeUseCases": [
          "M2M"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT M2M.",
      "universal": false,
      "remediation": "Write one sentence saying where consumers agree to receive the messages, then expand it into a full flow. Done when a reader can answer \"how did these people end up on this list?\" from the submission alone.",
      "example": "Customers opt in at checkout on acmecoffee.com by ticking a box to receive offers by text.",
      "notes": "The presence floor beneath CMP-059's quality bar. Kept separate because they fail and are fixed differently: CMP-059 grades a described mechanism against a standard, while this one asks whether a mechanism was described at all, and a submission failing this one fails CMP-059 for a reason that would mislead.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-091",
      "slug": "cmp-091",
      "title": "Implied consent needs a conversation the consumer started",
      "statement": "Implied consent is acceptable only where both the use case and the message flow are conversational, with the consumer sending first.",
      "rationale": "Implied consent exists for the case where a consumer texts a business and the business texts back, because the consumer initiating is the consent. Stretching it to cover a list the business assembled inverts that: nobody initiated anything, and the \"conversation\" is one-way. Businesses land here by reasoning from an existing relationship — these are our customers, they gave us their number — which is exactly the inference the standard exists to block.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.usecase + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Infobip",
          "code": "3013A",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Collect express consent for anything the consumer did not start: a checkbox, a keyword they texted, a form they submitted. Reserve implied consent for replies inside a thread the consumer opened. Done when every number on the list either texted you first or affirmatively opted in.",
      "example": "A customer texts Acme Coffee asking about store hours and we reply about store hours — that is the whole of what implied consent covers.",
      "pitfalls": [
        "A number collected during a purchase is not a consumer-initiated conversation; the purchase was the conversation, and it was not by text."
      ],
      "notes": "Conditional on implied consent actually being claimed, and we hold no such fact, so the condition lives in the criteria and the rule opens with a PASS boundary. CTIA is explicit that a one-way alert is never conversational however short the messages are.",
      "catalogIds": [
        "CON-119"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-092",
      "slug": "cmp-092",
      "title": "Consent grade must match content grade",
      "statement": "The consent standard evidenced must match the content grade: conversational needs implied consent, informational needs express consent, promotional needs express written consent.",
      "rationale": "Consent comes in grades and content comes in grades, and the content grade sets the floor. A promotional programme resting on an implied consent collected for a service purpose is the most common structural consent failure — it looks like consent exists, and it is the wrong kind, so the brand cannot understand the rejection and re-submits the same evidence.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "T-Mobile",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Raise the consent to match the content: promotional messaging needs express written consent — an affirmative, recorded agreement naming the sender and the message type. Re-collect it if what you hold was gathered for a service purpose; you cannot re-characterise consent after the fact. Done when the flow describes a written, affirmative act for every subscriber who will receive promotional content.",
      "example": "Promotional consent is collected in writing at checkout: the customer ticks an unchecked box beside the disclosure naming Acme Coffee and the message types, and we store the number, timestamp, and disclosure version.",
      "pitfalls": [
        "Splitting the campaign into a promotional one and a transactional one does not launder the consent — the promotional half still needs express written consent for each number on it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-094",
      "slug": "cmp-094",
      "title": "Each declared opt-in method needs its own evidence",
      "statement": "A screenshot or artifact is required for EACH opt-in method declared, not one artifact covering the set.",
      "rationale": "Methods differ structurally — a web checkbox, a paper form and a phone script share no visual elements — so one artifact cannot evidence several. Reviewers check them individually, and the missing ones are read as undocumented collection surfaces.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "consent artifacts + declared methods",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach one artifact per declared method: a screenshot for the web form, a scan for the paper form, the script for phone consent, a capture for the QR landing page.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-095",
      "slug": "cmp-095",
      "title": "A web opt-in must supply the form itself, not just describe it",
      "statement": "A web-form or checkout opt-in must supply the link to the opt-in page, or a screenshot of the form.",
      "rationale": "A reviewer confirms a web opt-in by looking at it, and a description with nothing to look at is an assertion rather than evidence. This is the most common shape of an incomplete submission because the flow text reads as complete — every element is described, and there is simply no way to check that any of it is on the page.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + opt-in evidence",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Bandwidth",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30896",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form and checkout.",
      "universal": false,
      "remediation": "Put the deep link to the page carrying the consent control in the message flow, and attach a screenshot of that control taken on a fresh page load. Done when someone with no account can open the link and see the box you described.",
      "pitfalls": [
        "A link to the homepage is not a link to the opt-in form. If the control is three clicks in, the screenshot is doing the work and the link is not enough on its own."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-096",
      "slug": "cmp-096",
      "title": "Keyword opt-in requires both the keywords and the confirmation text",
      "statement": "A keyword-based opt-in must populate both the opt-in keywords and the opt-in confirmation message.",
      "rationale": "The keyword is how the consumer joins and the confirmation is what tells them they succeeded; a keyword programme with no confirmation leaves them texting into silence, then texting again. Providers check the pair together because either alone is an incomplete programme.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optin_keywords + campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Populate campaign.optin_keywords with the keyword your signage advertises, and write the confirmation message carrying brand, frequency, rates, HELP and STOP. Done when texting the keyword would produce that exact reply.",
      "example": "optin_keywords: JOIN, START · optin_message: \"Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.\"",
      "pitfalls": [
        "The keyword in the campaign must match the keyword on the signage exactly — JOIN and JOINUS are different programmes to a reviewer comparing the two."
      ],
      "catalogIds": [
        "CMP-196"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-097",
      "slug": "cmp-097",
      "title": "A keyword opt-in must give the keyword and the number to text it to",
      "statement": "A keyword opt-in must state the exact keyword and the exact destination number in the message flow.",
      "rationale": "A reviewer verifies a keyword programme by texting the keyword to the number and seeing what comes back, and a flow missing either half makes that impossible. Both halves are usually known to everyone involved and written down nowhere, because the signage carries them and the form does not ask for them in so many words.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Aerialink",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Write the keyword and the destination number into the flow exactly as they appear on your signage, including the short code or long code digits. Done when someone holding only the flow text could join the programme from their own phone.",
      "example": "Supporters text SHELTER to 55512, as printed on the shelter posters.",
      "pitfalls": [
        "The keyword in the flow, the keyword on the signage and the keyword configured on the campaign have to be the same word — JOIN and JOINUS are different programmes to a reviewer comparing them."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-100",
      "slug": "cmp-100",
      "title": "The declared opt-in methods must match the flow described",
      "statement": "The opt-in methods ticked on the form must be the ones the message flow describes, in both directions.",
      "rationale": "The structured list is what routes the submission to the right evidence checks, and the narrative is what a human reads; when they disagree the campaign is graded against a rule set that does not fit it. A flow describing a QR poster with only \"web form\" ticked will be checked for a web opt-in page that does not exist, and the rejection names the missing page rather than the missing tick.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.opt_in_methods[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Tick exactly the methods your flow describes — MOBILE_QR_CODE for a scanned code, PAPER_FORM for a signed sheet, and so on — and delete any tick with no paragraph behind it. Done when each ticked method has a paragraph and each paragraph has a tick.",
      "example": "The flow describes a poster QR and a checkout box → opt-in methods MOBILE_QR_CODE and WEB_FORM, and nothing else.",
      "pitfalls": [
        "Fixing the mismatch by deleting the paragraph rather than ticking the box hides a collection surface you are still using, which is the more serious of the two failures."
      ],
      "notes": "One rule for two catalog rows: CMP-100 requires MOBILE_QR_CODE to be declared for a QR flow and CMP-106 requires PAPER_FORM for a paper one, which is a single enum-versus-narrative consistency check with the value changing. Deliberately not tagged to those two consent methods — the mismatch is worth catching whichever method is involved, and tagging it would exclude precisely the registrations whose ticks are wrong.",
      "catalogIds": [
        "CMP-106"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-101",
      "slug": "cmp-101",
      "title": "A QR opt-in must supply the QR code image itself",
      "statement": "A QR-code opt-in must include the QR image in the registration evidence.",
      "rationale": "The QR code is the only part of a QR flow a reviewer can independently verify: they scan it and see where it actually goes, which is the one thing the description cannot establish. A code that resolves somewhere other than the stated destination is the specific defect this evidence exists to catch, and it is invisible in every other field.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in evidence (OptInImageURLs)",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30896",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Attach the QR image exactly as it appears on the printed piece — the same code, not a regenerated one pointing at the same URL — and scan it yourself from a printout before submitting. Done when scanning the attached image lands on the opt-in destination the flow describes.",
      "pitfalls": [
        "Regenerating a \"clean\" QR for the submission defeats the check: the reviewer is verifying the code that is actually in the wild, and a code that has since been re-pointed will scan differently from the one on your poster."
      ],
      "notes": "The presence half is settled here; whether the scanned destination is a compliant opt-in surface is CON-QR-DEST and CMP-103, which read the destination rather than the file. Split because they fail differently and are fixed by different people — one by attaching a file, the other by rebuilding a landing page.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-103",
      "slug": "cmp-103",
      "title": "A QR flow must say where the code leads",
      "statement": "A QR opt-in must state whether the code opens a hosted opt-in form or a pre-filled SMS in the native messaging app.",
      "rationale": "Both destinations are permitted and they are graded by completely different rules — a hosted form owes a checkbox and a disclosure, a pre-filled SMS owes a confirmation reply and keyword handling — so a reviewer who cannot tell which one this is cannot begin. Submitters omit it because from their side there is only one obvious answer, and which one is obvious depends on who built the poster.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + QR destination",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Klaviyo"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Say which of the two the code does, and give the destination: the opt-in URL, or the number and body of the pre-filled message. Done when the flow names the destination type in words rather than leaving it to be inferred from the URL.",
      "example": "Scanning the poster QR opens a pre-filled SMS to 55512 with the body JOIN; the customer presses send, which is the opt-in.",
      "notes": "Neither destination is preferred — the rule is about the flow saying which one, not about choosing between them. Whether a hosted destination is a specific opt-in page rather than the homepage is CMP-064, which reads the URL itself.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-112",
      "slug": "cmp-112",
      "title": "A promotional programme cannot rest on spoken consent alone",
      "statement": "A campaign whose content requires express written consent must not declare a spoken opt-in as its only collection method.",
      "rationale": "Written consent exists so that what the consumer agreed to can be produced years later when a complaint arrives, and a phone call produces no such record — an agent's note that the customer said yes cannot show what they were told they were saying yes to. Businesses reach this honestly: the call centre already asks for consent for service messages, and adding the marketing programme to the same script looks like a small extension rather than a change of consent grade.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.usecase + declared consent methods",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "paper_consent_form",
      "authorities": [
        "FCC",
        "Twilio",
        "Infobip",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30913",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "2101",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Collect the promotional consent in writing — a signed form at the counter, or a web opt-in the customer completes themselves — and keep the spoken path for the service messages it is valid for. Done when every number receiving promotional content has a written record behind it.",
      "pitfalls": [
        "Numbers already collected verbally cannot be moved onto the written programme by re-characterising the consent; they have to be asked again.",
        "Reading the written disclosure aloud does not make the consent written. What matters is the record the consumer produced, not the words the agent said."
      ],
      "notes": "The use-case condition lives in the check rather than in an applicability tag, because it is read off USE_CASE_REQUIREMENTS.consentGrade — a table row rather than a fixed list, so a use case added later is covered without editing this rule. The tag narrows to the spoken methods, which is what makes the rule inapplicable to a web or checkout opt-in instead of passing trivially on one. The legal layer records the Fifth Circuit *Bradley v. DentalPlans.com* carve-out; carriers do not honour it, so the registry does not either.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-113",
      "slug": "cmp-113",
      "title": "Debt collection cannot rest on a spoken opt-in",
      "statement": "A debt-collection campaign must not collect its consent verbally.",
      "rationale": "Debt collection is already the most heavily litigated messaging category, and a spoken consent produces no record of what the debtor was told — so the one category where the consent record is most likely to be examined is the one where it would be weakest. The overlap with third-party collection matters too: that is refused outright at most providers, so a verbal opt-in here often signals a programme that has a larger problem than its consent method.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + brand.vertical",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "paper_consent_form",
      "authorities": [
        "Aerialink"
      ],
      "applicability": {
        "verticals": [
          "debt_collection"
        ]
      },
      "applicabilityText": "Applies when the vertical is debt_collection.",
      "universal": false,
      "remediation": "Collect written consent for the debt-collection programme — a signed form at account opening, or a portal opt-in the debtor completes themselves — and keep a copy of the disclosure version each debtor saw. Done when every number on the programme has a written record behind it.",
      "pitfalls": [
        "Consent obtained by the original creditor does not transfer to a third-party collector, so re-collecting is not optional even where a written record exists upstream."
      ],
      "notes": "Narrower than CMP-112, which refuses spoken consent for promotional content generally: this one refuses it for a whole vertical regardless of use case, because the exposure comes from the subject matter rather than from the content grade. Third-party debt collection is separately prohibited outright by the MSG-DEBT prohibition, which fires on content rather than on vertical.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-115",
      "slug": "cmp-115",
      "title": "A point-of-sale opt-in must show the sign the customer read",
      "statement": "A point-of-sale opt-in must supply a hosted photo of the signage, kiosk or tablet screen carrying the disclosure.",
      "rationale": "An in-store opt-in leaves nothing on the web to inspect, so the photograph is the entire evidentiary record of what the customer was shown before they gave their number. Counter signage is also where disclosures are most often abbreviated for space — the frequency and rates lines are the first to go — which is exactly what the photo is there to reveal.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "hosted evidence URL",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "CTIA",
        "Twilio",
        "TextUs",
        "HighLevel"
      ],
      "applicability": {
        "consentMethods": [
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by point of sale.",
      "universal": false,
      "remediation": "Photograph the sign or the tablet screen where it stands, close enough to read every line of the disclosure, and host it at a public URL cited in the flow. Done when the disclosure text is legible in the image without zooming.",
      "pitfalls": [
        "A photograph of the artwork file is not a photograph of the sign. Reviewers are checking that the disclosure is actually displayed at the point of collection, not that it was designed."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-116",
      "slug": "cmp-116",
      "title": "An email-activated opt-in must show the email that asked",
      "statement": "Where the opt-in is solicited by email, a screenshot of that email must be supplied.",
      "rationale": "The email is where the consumer was told what they were joining, so it carries the disclosures the web form usually would. Businesses treat it as marketing collateral rather than as consent evidence and often no longer have the exact version that was sent, which is why the screenshot is asked for at registration rather than at complaint time.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in evidence",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Bandwidth"
      ],
      "applicability": {
        "consentMethods": [
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by email.",
      "universal": false,
      "remediation": "Send yourself the live activation email, screenshot it whole — subject line, body, and the link or button that starts the opt-in — and host it. Done when the image shows what the recipient saw, not a template preview.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-118",
      "slug": "cmp-118",
      "title": "A pop-up opt-in must be declared as one",
      "statement": "Where the opt-in is presented in a pop-up or modal, the message flow must say so.",
      "rationale": "A reviewer opening the page sees no consent control, because the modal fires on a timer, on exit intent, or only for new visitors — so a perfectly compliant pop-up opt-in looks like a page with no opt-in on it. Saying \"the opt-in appears in a modal after a few seconds on first visit\" is the difference between an approval and a rejection for a form that was there the whole time.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form and checkout.",
      "universal": false,
      "remediation": "State that the opt-in is a pop-up, what triggers it, and attach a screenshot of it open. Done when a reviewer who sees no form on the page has already been told why and has an image of it.",
      "example": "The opt-in appears in a pop-up shown to first-time visitors after 5 seconds on https://acmecoffee.com; screenshot attached: https://cdn.acmecoffee.com/compliance/popup-optin.png",
      "notes": "Tagged to the web methods because a modal is a web presentation; a spoken or paper opt-in cannot have one. The rule asks only that the presentation be declared — whether a pop-up is an acceptable surface at all is CON-DISC-NO-POPUP, which grades the disclosure inside it.",
      "catalogIds": [
        "CON-050"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-119",
      "slug": "cmp-119",
      "title": "Consent collected on a third-party platform must still name the registered brand",
      "statement": "Consent evidence captured on a third-party platform must show the registered brand as the party the consumer agreed to hear from.",
      "rationale": "A consumer who opts in inside a marketplace, a social platform or a chat widget is agreeing to hear from whoever is named on that screen. When the screen names only the platform, the consent belongs to the platform and not to the brand sending the messages — which is the consent-transfer problem the framework exists to prevent, arrived at by accident rather than by design.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in evidence captured on a third-party surface",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30927",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "third_party",
          "social",
          "chat"
        ]
      },
      "applicabilityText": "Applies when consent was collected by third party, social and chat.",
      "universal": false,
      "remediation": "Configure the third-party surface to name your business in the consent text — most let you set the merchant or sender name — and re-capture the screenshot afterwards. Done when the registered brand name is visible in the same frame as the consent control.",
      "pitfalls": [
        "The platform's own branding in the header does not substitute for naming your business in the consent sentence, and a reviewer reads the sentence rather than the chrome."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-122",
      "slug": "cmp-122",
      "title": "The opt-in screenshot must show the terms and privacy links",
      "statement": "A screenshot submitted as opt-in evidence must show the Terms and Privacy Policy links as the consumer sees them.",
      "rationale": "The links being present on the page is not the question — the question is whether they were visible at the moment of consent, and only the screenshot answers it. A footer link three screens below the form is on the page and was not part of the decision, which is the distinction reviewers are looking for when they ask for this framing.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in screenshot",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "LeadConnector",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form and checkout.",
      "universal": false,
      "remediation": "Re-frame the screenshot to include the consent control and the terms and privacy links in one image. If they do not fit in one frame, move the links up beside the control rather than stitching two screenshots together.",
      "pitfalls": [
        "Stitching a crop of the form to a crop of the footer proves the opposite of what it is meant to: it shows the two were not visible together."
      ],
      "catalogIds": [
        "POL-036"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-124",
      "slug": "cmp-124",
      "title": "A multi-step consent flow must be captured at every step",
      "statement": "Where consent is collected across several screens, evidence must show each step rather than the final one.",
      "rationale": "In a multi-step flow the disclosure and the consent control are usually on different screens, so a screenshot of the step carrying the button proves nothing about what was disclosed before it. This is the flow shape where a compliant programme most often looks non-compliant — everything required was shown, and the evidence only covers the last screen of it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in screenshots",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio",
        "LeadConnector",
        "optinfix"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30917",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Capture every screen between arriving and consenting, in order, and combine them into one PDF. Done when the sequence shows the disclosure and the consent act without a gap a reviewer has to fill in.",
      "example": "A three-page PDF: the product page with the offer, the form with the phone field and disclosure, the confirmation screen after submitting.",
      "catalogIds": [
        "CON-032"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-127",
      "slug": "cmp-127",
      "title": "A consent surface a reviewer cannot reach must be declared as such",
      "statement": "Where the opt-in sits behind a login, is unpublished, is spoken, or is on paper, the submission must say so explicitly.",
      "rationale": "A reviewer who cannot find the opt-in assumes it does not exist, because that is far more often true than the alternative. Saying \"this opt-in is inside the customer account portal, screenshot attached\" converts a dead end into a documented one — the same registration, differing only in whether the reviewer had to guess.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add one sentence naming why the surface is not reachable and what stands in for it. Done when the flow text answers the question a reviewer would otherwise have to ask.",
      "example": "The opt-in toggle sits inside the authenticated account portal and cannot be crawled, so a screenshot taken from a live account is attached: https://cdn.acmecoffee.com/compliance/portal-optin.png",
      "catalogIds": [
        "CON-021"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-128",
      "slug": "cmp-128",
      "title": "An evidence field with nothing to say should be left empty",
      "statement": "Evidence fields that do not apply must be omitted rather than filled with filler text.",
      "rationale": "Filler in an evidence field converts a question a reviewer would have skipped into one they now have to resolve, and \"N/A\" beside a field they expected content in reads as a refusal rather than an absence. It comes from forms that mark every field required, so the submitter types something to get past validation and inherits a finding.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "evidence fields",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30917",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Clear any field holding \"N/A\", \"none\", \"see above\" or a repeat of another field. Where a form will not accept an empty value, say in one clause why the field does not apply. Done when every populated field carries information.",
      "example": "A campaign with no paper opt-in leaves the paper-form evidence field empty — not \"N/A - we do not use paper forms, see web form above\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-129",
      "slug": "cmp-129",
      "title": "Evidence must come from one company, not several",
      "statement": "A campaign's opt-in evidence must all come from the registered brand, not from subsidiaries, franchises or client brands.",
      "rationale": "A bundle showing three businesses tells a reviewer that consent was pooled across them, and pooled consent is not transferable to whichever entity happens to be sending. Agencies and franchisors hit this honestly by attaching the best examples they have rather than the ones belonging to the registrant, and the submission is refused for a reason that reads as pedantic until you notice it is the whole question.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "opt-in evidence across the bundle",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30926",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach only artifacts captured from this brand's own consent surfaces, and register a separate campaign for each other entity. Where the programme genuinely spans franchisees, the AGENTS_FRANCHISES use case is the route that lets sub-entities be disclosed rather than hidden.",
      "example": "Every screenshot shows acmecoffee.com — not one from acmecoffee.com and one from a sister brand that happens to run the same form.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-131",
      "slug": "cmp-131",
      "title": "Evidence must travel by the route the submission target actually offers",
      "statement": "Evidence must reach the reviewer through the channel the submission target provides, whether an upload field, a TCR attachment, or a URL inside the flow.",
      "rationale": "Every platform exposes exactly one evidence channel and none of them is the same: one has a multimedia upload field, one attaches the form to TCR, one accepts files only in its console and needs a URL in the payload when you use the API. Evidence lodged in the wrong place is not late or malformed — it is invisible, and the rejection reads as though nothing was ever attached.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "evidence channel + campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "Bird"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use your target's channel: Bandwidth's \"CTA, Privacy Policy and/or Terms and Conditions Multimedia Upload\" field; a TCR attachment on the express-consent form for Telnyx; Bird's console attachments, or the proof URL inside the messageFlow JSON when you submit by API because attachments there are UI-only. Done when the evidence is reachable from the submission itself rather than from your own console.",
      "example": "Submitting to Bird by API: messageFlow ends with \"Opt-in proof: https://cdn.acmecoffee.com/compliance/optin.png\" because attachments cannot be sent over the API.",
      "pitfalls": [
        "Attaching in the console and submitting by API is the combination that silently loses the evidence: the file is visible to you and absent from the payload the reviewer receives."
      ],
      "notes": "Three catalog rows, one obligation with the destination as its parameter — Bandwidth's upload field (CMP-131), Telnyx's TCR attachment (CMP-133) and Bird's API workaround (CMP-134). Written as one rule because a submitter has exactly one target and needs exactly one instruction; three rules would report the same gap three times with two of them irrelevant. We can see that evidence exists and whether the flow text carries a URL, but not which UI field a file was uploaded through, so the check settles the observable half and the remediation carries the rest.",
      "catalogIds": [
        "CMP-133",
        "CMP-134"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-132",
      "slug": "cmp-132",
      "title": "Where the platform has no upload field, proof must be a URL in the flow text",
      "statement": "On a platform offering no evidence upload, every artifact must be reachable from a public URL written into the message-flow text.",
      "rationale": "Some platforms surface no file-upload field anywhere in campaign registration, so evidence attached in your own console never reaches the reviewer — they read the message-flow text and nothing else. The submission then looks complete from your side and arrives empty at theirs, which is why the rejection reason is \"no opt-in evidence\" on a campaign whose evidence you can see.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow + hosted evidence URLs",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30921",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Host each artifact at a URL that opens without a login and paste those URLs into the message-flow text itself, labelled with what each one shows. Done when the flow text alone would let a reviewer reach every piece of evidence.",
      "example": "Screenshot of the checkout consent box: https://cdn.acmecoffee.com/compliance/optin.png · Verbal script: https://cdn.acmecoffee.com/compliance/script.pdf",
      "notes": "The research marks the \"no upload path exists\" finding as an inference drawn from uniform Twilio wording across eight rejection codes rather than from a published statement, so the provider condition is applied in the check and the rule passes on any other target. Cite the underlying obligation — evidence must reach the reviewer — rather than this phrasing if a user pushes back.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-139",
      "slug": "cmp-139",
      "title": "Each declared sub-use case needs its own sample message",
      "statement": "A campaign declaring sub-use cases must carry at least one sample message for each of them.",
      "rationale": "A MIXED campaign is several programmes sharing one registration, and a reviewer approves each of them on the strength of a sample. Declaring five sub-use cases and supplying two samples asks them to approve three programmes they have not seen, so they refuse — and the rejection names the sample count, which sends the submitter to add generic samples rather than to the sub-use case that had nothing behind it.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[] + campaign.sub_use_cases[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "AWS",
        "Sinch",
        "Bird",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "8102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write one sample per declared sub-use case, each showing that sub-use case and nothing else. Done when you can point at the sample that justifies every sub-use case you ticked — and if you cannot write one, untick it.",
      "example": "Sub-use cases MARKETING + DELIVERY_NOTIFICATION → sample1 \"Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out.\" and sample2 \"Acme Coffee: your order #4471 ships today. Reply STOP to opt out.\"",
      "pitfalls": [
        "Reusing one promotional sample for two promotional sub-use cases does not satisfy this — the reviewer is counting distinct programmes, not distinct wordings."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-141",
      "slug": "cmp-141",
      "title": "Sample count must meet the minimum for the declared use case",
      "statement": "The campaign must carry at least the number of sample messages its use case requires.",
      "rationale": "Sample minimums vary by use case, and special use cases generally require more because reviewers need to see the breadth of a programme that gets elevated treatment. A campaign that satisfies the global minimum can still fail its own use case.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add sample messages until the use-case minimum is met, each showing a genuinely different scenario rather than a reworded duplicate.",
      "example": "MARKETING requires at least 2 distinct samples.",
      "catalogIds": [
        "CMP-136"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-142",
      "slug": "cmp-142",
      "title": "Use case must be available to the brand entity type",
      "statement": "Restricted use cases must only be selected by the entity types eligible for them.",
      "rationale": "Several use cases are gated on entity type — charity to non-profits, sole-proprietor to individuals, emergency to registered organisations. Selecting one the brand cannot hold produces a rejection that reads as a campaign problem while the real fix is on the brand.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.usecase + brand.entity_type",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Infobip"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either choose a use case your entity type can hold, or re-register the brand under the entity type the use case requires.",
      "example": "CHARITY requires entity type NON_PROFIT.",
      "catalogIds": [
        "BRD-159"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-175",
      "slug": "cmp-175",
      "title": "Subscriber attributes must be true where the API accepts nothing else",
      "statement": "On a CSP whose API accepts only true for the subscriber opt-in, opt-out and help attributes, none of the three may be submitted false.",
      "rationale": "This is a schema rejection rather than a vetting one: the call fails before the campaign exists, so there is no rejection reason to read and no campaign record to inspect. It bites exactly the submissions that are entitled to say false — a two-factor programme relying on the opt-out exemption TCR grants finds that its provider never implemented the exemption.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.subscriber_optin + subscriber_optout + subscriber_help",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Plivo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send true for all three attributes on this provider, and back each one with real handling — declared keywords and a reply body — rather than leaning on the use-case exemption. Done when the payload carries three true values and three configured behaviours.",
      "example": "subscriber_optin: true · subscriber_optout: true · subscriber_help: true",
      "notes": "Plivo publishes \"Allowed value: true\" for all three fields. The provider condition is applied inside the check rather than as a `providers` tag: a tag resolves YES when the submission target is unknown, which would make this rule contradict CON-010's two-factor carve-out on every registration that has not chosen a provider yet. Passing on an unknown target is the honest reading — the exemption is real everywhere except here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-176",
      "slug": "cmp-176",
      "title": "Declaring opt-in requires opt-in keywords where the provider pairs them",
      "statement": "Where the provider ties the two fields together, declaring subscriberOptin true requires optinKeywords to be populated.",
      "rationale": "The provider models opt-in as keyword-driven and validates the pair, so a web-form programme that has no keyword to give is rejected for a field it had no reason to fill in. The fix is not to invent a keyword but to declare the ones the programme already honours — START and its variants are answered by every platform whether or not anyone advertises them.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optin_keywords + campaign.subscriber_optin",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bird"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Populate the opt-in keyword field with the keywords your platform actually answers — START and UNSTOP at minimum, plus any keyword your signage advertises. Done when the field is non-empty and every keyword in it triggers a subscription on your side.",
      "example": "subscriberOptin: true · optin_keywords: START, UNSTOP, JOIN",
      "notes": "Bird's campaign form makes optinKeywords conditionally mandatory on subscriberOptin. The condition is applied inside the check rather than as a `providers` tag, because a tag resolves YES on an unknown submission target and would then demand opt-in keywords from every web-form and checkout registration in the registry — TCR requires them only for keyword opt-in (CMP-096).",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-187",
      "slug": "cmp-187",
      "title": "The wider opt-out keyword set is required on some platforms",
      "statement": "Where the platform publishes a wider standard opt-out set, the declared keywords must cover HALT and STOPALL as well as the universal five.",
      "rationale": "A consumer who texts HALT has opted out as far as they are concerned, and on a platform that treats HALT as a recognised opt-out the message is suppressed whether or not you declared it — so a declaration that omits it puts your suppression list out of step with what the platform has already done. You then keep sending to someone the platform believes unsubscribed, which is the pattern complaint volume is measured on.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30887",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add HALT and STOPALL to the declared opt-out keywords, and confirm your platform suppresses on both. Done when the declared set covers STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, HALT, OPTOUT and REVOKE.",
      "example": "optout_keywords: STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, HALT, OPTOUT, REVOKE",
      "notes": "Twilio's published set adds HALT and STOPALL to the seven FCC per-se revocation keywords that CON-KW-REVOCATION-SET requires. Kept out of the universal baseline deliberately: neither the FCC nor CTIA lists them, so demanding them everywhere would fail compliant registrations to satisfy one platform. On an unknown submission target the rule passes and names the two additions rather than failing, because we cannot yet know whether they are owed. An incomplete or empty list is OPS-026 and OPS-027's finding, not this one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-188",
      "slug": "cmp-188",
      "title": "A non-standard opt-out keyword is honoured by nobody but you",
      "statement": "Opt-out keywords outside the standard monitored set are handled by the sending platform alone and must not be relied on as carrier-enforced.",
      "rationale": "The standard keywords are intercepted upstream, so they work even when a platform is misconfigured; anything else works only for as long as your own handler does. A programme whose advertised opt-out word is bespoke has a single point of failure between the consumer and their opt-out, and the consumer who used it and kept receiving messages complains to the carrier rather than to you.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optout_keywords",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Azure ACS",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep the bespoke keyword if your platform answers it, but add the standard words alongside so the opt-out survives a misconfiguration on your side. Done when every keyword you advertise for opting out is either standard or backed by one that is.",
      "example": "optout_keywords: STOP, END, CANCEL, UNSUBSCRIBE, QUIT — not STOPMSGS or NOMORE on their own",
      "notes": "Scoped to the opt-out list rather than to every keyword field, which is what the catalog row names. A non-standard HELP keyword costs a recipient an unanswered question; a non-standard opt-out keyword costs them an opt-out that never happened, and only the second is worth a finding. INFO and SUPPORT as help keywords are normal practice and are deliberately not reported here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-AFFILIATE",
      "slug": "cmp-affiliate",
      "title": "Affiliate marketing campaigns are refused",
      "statement": "A campaign declaring affiliate marketing — sharing opt-ins with, or promoting, third parties — is rejected.",
      "rationale": "Affiliate marketing means messaging people whose consent someone else collected, which is the transfer of consent the entire framework prohibits. Declaring it is a self-reported disqualification, and no use case or disclosure makes it registrable.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.affiliate_marketing",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "TCR",
        "Twilio",
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Affiliate and lead-generation messaging cannot be registered. Consent is non-transferable: only the business that collected it may message the consumer.",
      "catalogIds": [
        "CON-016"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-AUTOREPLY-LEN",
      "slug": "cmp-autoreply-len",
      "title": "Auto-reply messages must be 20–320 characters",
      "statement": "The opt-in, opt-out and HELP reply messages must each be 20–320 characters.",
      "rationale": "Auto-replies must fit the carrier limits for a single message, and a reply truncated mid-sentence loses precisely the disclosure it exists to deliver. The floor of 20 characters exists because a two-word reply cannot carry the brand name and a contact route.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.help_message + optin_message + optout_message",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Trim each auto-reply to fit within 320 characters while keeping the brand name, the STOP instruction and the rates notice — those are the elements other rules require. If it will not fit, drop the marketing language rather than the disclosures.",
      "example": "Acme Coffee: for help email support@acmecoffee.com. Reply STOP to unsubscribe.",
      "notes": "Bird and Sinch cap at 255; Twilio and Bandwidth at 320. The 320 ceiling is used with the tighter 255 recorded as a provider divergence.",
      "catalogIds": [
        "CMP-007",
        "CMP-008",
        "CMP-009"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-DESC-LEN",
      "slug": "cmp-desc-len",
      "title": "Campaign description must be 40–4096 characters",
      "statement": "The campaign description must be at least 40 and at most 4096 characters.",
      "rationale": "The published bound is 40 to 4096 characters, and anything near the floor cannot contain the audience, content and opt-in source a reviewer needs. Enforcing the real bound rather than a guessed heuristic also prevents the opposite failure, where a long, thorough description is silently truncated on submission.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Expand the description to explain who receives the messages, what they contain, and how recipients opted in. Aim well above the 40-character floor.",
      "example": "Acme Coffee sends weekly promotional offers, new product announcements, and loyalty rewards updates to customers who opted in via the checkout consent checkbox at acmecoffee.com.",
      "notes": "The current contentFlags scanner uses a 40/100 heuristic with no upper bound — this replaces the guess with the published bound.",
      "catalogIds": [
        "CMP-001"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-EMOJI",
      "slug": "cmp-emoji",
      "title": "Registration fields must not contain emoji",
      "statement": "No campaign registration field may contain an emoji character.",
      "rationale": "Emoji are fine in live message traffic but rejected inside the registration record itself, because the downstream systems that parse and display campaign metadata do not handle them consistently. It surfaces only in the DCA vetting reason list rather than in any provider documentation, which is why generic implementations never check for it — and why a generator that writes a cheerful emoji into a description silently creates a rejection.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.* (all text fields)",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "TCR/DCA",
          "code": "6103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove emoji from the registration fields. They are allowed in live message traffic, but not in the registration record itself.",
      "example": "Acme Coffee sends weekly promotional offers to customers who opted in at checkout.",
      "notes": "Surfaces only in the DCA vetting reason list, not in any provider doc — which is why generic implementations miss it entirely.",
      "catalogIds": [
        "CMP-015"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-FLOW-LEN",
      "slug": "cmp-flow-len",
      "title": "Message flow must be 40–2049 characters",
      "statement": "The opt-in / message-flow field must be at least 40 and at most 2049 characters.",
      "rationale": "The message flow has to hold the opt-in location, the consent wording, and a reference to the evidence, which does not fit in a sentence. The 2049-character ceiling is tighter than the description's, so a thorough flow can be truncated on submission without warning.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe where and how consent is collected, including the exact disclosure wording shown to the consumer.",
      "example": "Customers opt in at https://acmecoffee.com/signup by ticking an unchecked box reading \"By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee…\". Screenshot: https://cdn.acmecoffee.com/compliance/optin.png",
      "catalogIds": [
        "CMP-002"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-HTTPS",
      "slug": "cmp-https",
      "title": "Every URL must use HTTPS",
      "statement": "All URLs in the registration must use https://; plain http:// is rejected.",
      "rationale": "A plain http:// link exposes the recipient to interception on exactly the channel carriers are trying to make trustworthy, so it is refused. This is the single highest-consequence cheap defect in the catalog: Twilio's current code for it is NOT eligible for resubmission, so one forgotten \"s\" can permanently kill a campaign rather than earning a fixable rejection.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[] + campaign.message_flow + urls",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Infobip",
        "TCR",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30964",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Infobip",
          "code": "10900",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace every http:// link with its https:// equivalent and confirm the certificate is valid.",
      "example": "https://acmecoffee.com/sale",
      "notes": "Twilio 30964 is NOT eligible for resubmission — a plain http:// link can permanently kill a campaign. Highest severity-to-effort ratio in the whole catalog.",
      "catalogIds": [
        "MSG-097"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-KEYWORD-FMT",
      "slug": "cmp-keyword-fmt",
      "title": "Opt-out and HELP keywords must be alphanumeric and under 255 characters",
      "statement": "Opt-out and help keyword lists must contain only alphanumeric keywords and must not exceed 255 characters.",
      "rationale": "Keywords are matched as single alphanumeric tokens, so a keyword containing a space or punctuation can never be triggered by an inbound message — it silently never works. The product's own form placeholder currently suggests a non-conforming value, which propagates the defect into every registration that accepts the default.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.optout_keywords + campaign.help_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use plain alphanumeric keywords separated by commas, e.g. \"STOP, UNSUBSCRIBE, CANCEL\".",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "notes": "The form placeholder currently suggests \"OPT OUT\" style keywords with a space, which violates this rule — the product ships a non-conforming exemplar.",
      "catalogIds": [
        "CMP-011",
        "CMP-012"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-LINK-DECL",
      "slug": "cmp-link-decl",
      "title": "embeddedLink must match whether samples actually contain links",
      "statement": "The embeddedLink attribute must be true when any sample contains a URL, and a sample must contain a URL when it is declared true.",
      "rationale": "The embedded-link attribute drives carrier filtering, so a mismatch between the declaration and the samples misroutes the campaign's traffic. It fails in both directions: undeclared links look like evasion, and a declared-but-absent link means the campaign was registered for treatment it does not need.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.embedded_link + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the \"messages contain links\" flag to match your sample messages, in both directions.",
      "example": "Samples contain https://acmecoffee.com/sale → set \"messages contain links\" to true.",
      "notes": "Bidirectional. The current contentFlags scanner only checks one direction, and does so with a stateful /g regex.",
      "catalogIds": [
        "MSG-118"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-PHONE-DECL",
      "slug": "cmp-phone-decl",
      "title": "embeddedPhone must match whether samples contain phone numbers",
      "statement": "The embeddedPhone attribute must be true when any sample contains a phone number, and vice versa.",
      "rationale": "Phone numbers inside message bodies are a filtering signal because they are a common redirection tactic, so the declaration must match reality. Several providers carve out a support number in the HELP reply, which is why this is graded below the link rule.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.embedded_phone + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the \"messages contain phone numbers\" flag to match your sample messages.",
      "example": "Samples contain 415-555-0134 → set \"messages contain phone numbers\" to true.",
      "notes": "A HELP-reply support number is an accepted exception at several providers; that carve-out is not yet modelled here.",
      "catalogIds": [
        "MSG-116"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-PLACEHOLDER",
      "slug": "cmp-placeholder",
      "title": "Registration fields must not contain placeholder text",
      "statement": "No registration field may contain lorem-ipsum, obvious placeholder text, or unreplaced template tokens.",
      "rationale": "Placeholder text and unreplaced template tokens tell a reviewer the registration was never finished, and they correlate with campaigns that later send something entirely different. An unreplaced \"[Your Business]\" is worse than a blank field because it looks deliberate.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.* (all text fields)",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace placeholder text with the real content the campaign will use.",
      "example": "Acme Coffee: your order #4471 is ready for pickup. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "MSG-022"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-SAMPLE-COUNT",
      "slug": "cmp-sample-count",
      "title": "At least two sample messages are required",
      "statement": "A campaign must carry at least two, and at most five, sample messages.",
      "rationale": "Two samples is the operative minimum across providers even where a form claims one is enough — AWS's own form says \"at least one\" while its reviewer rejects below two. The upper bound of five is a hard field limit.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Provide at least two distinct sample messages that reflect what the campaign will actually send.",
      "example": "Provide 2 to 5 distinct sample messages.",
      "notes": "AWS's own form says \"at least one\" while its reviewer rejects below two. Two is the operative minimum across providers.",
      "catalogIds": [
        "CMP-006",
        "CMP-136",
        "CMP-138"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-SAMPLE-DUP",
      "slug": "cmp-sample-dup",
      "title": "Sample messages must not be duplicates",
      "statement": "Sample messages must be materially different from one another.",
      "rationale": "Identical samples pad a field count without showing a reviewer anything about the program. This catches the exact-duplicate case cheaply in code; the harder question of whether two differently-worded samples are materially different is judged separately by MSG-SAMPLES-DISTINCT.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace duplicated samples with genuinely different messages the campaign will send.",
      "example": "Each sample should describe a different scenario: a promotion, an order update, an event.",
      "catalogIds": [
        "CMP-149"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-SAMPLE-LEN",
      "slug": "cmp-sample-len",
      "title": "Each sample message must be 20–1024 characters",
      "statement": "Every sample message must be at least 20 and at most 1024 characters.",
      "rationale": "A sample under 20 characters cannot demonstrate a real message, and one over 1024 is not a text message at all. Both bounds are published and enforced mechanically at submission.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write realistic sample messages of at least 20 characters each.",
      "example": "Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "CMP-005"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-URL-DISTINCT",
      "slug": "cmp-url-distinct",
      "title": "Privacy policy and terms URLs must be different pages",
      "statement": "The privacy policy URL and the terms of service URL must not be the same page.",
      "rationale": "The privacy policy and the terms answer different questions — how data is handled versus what the program is — and reviewers check them separately. Brands routinely paste the same combined-legal-page URL into both fields, and a presence-only check passes it while a reviewer opening both links does not.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.privacy_url + campaign.terms_url",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the privacy policy and the terms of service separately. If you only have one combined page, publish a distinct SMS terms page.",
      "example": "Privacy: https://acmecoffee.com/privacy · Terms: https://acmecoffee.com/terms",
      "notes": "Catalog POL-017. Brands paste the same URL twice constantly and \"both fields present\" passes today.",
      "catalogIds": [
        "POL-017"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CMP-USECASE-ENUM",
      "slug": "cmp-usecase-enum",
      "title": "Use case must be a recognised TCR value",
      "statement": "The declared use case must be one of the recognised TCR use-case values.",
      "rationale": "The use case drives the consent standard, the throughput granted, and the filtering applied, so an unrecognised value cannot be processed at all. Free-text or dropdown-label values coerced silently to a catch-all are worse than an error, because the campaign is then judged against rules nobody chose.",
      "layer": "CAMPAIGN",
      "layerSlug": "campaign",
      "object": "campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Select a use case from the standard TCR list rather than typing a description. If none fits exactly, MIXED is the catch-all — but it carries promotional-grade consent obligations.",
      "example": "Use case: MARKETING",
      "pitfalls": [
        "Silent coercion of an unrecognised value to MIXED hides the problem and changes which rules apply."
      ],
      "notes": "Carries a descriptive id rather than a catalog one because no catalog row states this requirement; it is the precondition the use-case matrix rules depend on. It previously carried the id CMP-140, which belongs to an unrelated row (\"read mnoMetadata.minMsgSamples per MNO\") and made that row read as covered when nothing implements it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-008",
      "slug": "con-008",
      "title": "The live CTA and the declared message flow must say the same thing",
      "statement": "The consent wording quoted in the message flow must be the wording actually printed on the live opt-in surface.",
      "rationale": "The flow is the brand's account of the consent and the page is the consent itself, so where they differ the page wins and the registration describes something that does not happen. It is rarely dishonest: the flow was written from the disclosure the team intended to ship, and the page was changed afterwards by someone who never saw the campaign record.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow vs live opt-in page",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Twilio",
        "TCR"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social and email.",
      "universal": false,
      "remediation": "Copy the consent sentence off the live page into the message flow verbatim, rather than paraphrasing it. Where the page is the thing that is wrong, fix the page first and then quote it. Done when the quoted sentence and the printed one match word for word.",
      "example": "Customers opt in at https://acmecoffee.com/checkout by ticking an unchecked box reading: \"Text me Acme Coffee offers and rewards updates.\" — quoted exactly as printed.",
      "pitfalls": [
        "Tidying the punctuation or capitalisation while copying is enough to read as a different disclosure to a reviewer comparing them side by side."
      ],
      "notes": "The catalog classes this DETERMINISTIC, and it would be, given a crawl that says WHICH page each block of text came from. The registry carries one undifferentiated websiteText, so a string comparison cannot tell \"the CTA was reworded\" from \"we fetched the homepage and the CTA is at /checkout\" — and failing a compliant registration for the second is the worse error. Authored as judged until the crawler carries a per-page map; both audits the catalog asks for (CON-009) fall out of the same comparison.",
      "catalogIds": [
        "CON-009"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-010",
      "slug": "con-010",
      "title": "The three subscriber-management attributes must be declared true",
      "statement": "The subscriberOptin, subscriberOptout and subscriberHelp campaign attributes must each be set true, and the HELP declaration must have a keyword set behind it.",
      "rationale": "These attributes assert that the programme collects consent, honours opt-outs and answers HELP, which is the baseline claim a campaign makes about itself. Leaving any of them false or unset contradicts every other field in the submission and is a fast, mechanical rejection — Plivo will not even accept the API call. They are also what a carrier relies on when a complaint lands, so declaring HELP with no keyword configured is a claim you will be held to and cannot meet.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.subscriber_optin + subscriber_optout + subscriber_help",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Plivo",
        "Twilio"
      ],
      "applicability": {
        "excludeUseCases": [
          "M2M"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT M2M.",
      "universal": false,
      "remediation": "Set all three attributes true, declare at least one HELP keyword, and make sure the claims are real — opt-in evidence attached, opt-out keywords honoured by your platform, and a HELP reply configured. Done when the declaration matches what your platform actually does.",
      "example": "subscriberOptin: true · subscriberOptout: true · subscriberHelp: true · help_keywords: HELP, INFO",
      "notes": "Three catalog rows, one declaration block: CON-010 (opt-in) is universal except M2M; CON-011 (opt-out) additionally exempts 2FA, which the check applies rather than the applicability tag, since only part of the rule is exempt; CON-012 (help) is universal. The registry spells that use case TWO_FACTOR_AUTH; the catalog spells it 2FA. CON-012 also requires a HELP reply body behind the keyword set — that half is CON-HELP-BACKED, so this rule stops at the keyword set to avoid reporting the same missing message twice. Whether HELP is really answered at runtime is UNDETECTABLE_PRE_SUBMISSION and is not claimed here.",
      "catalogIds": [
        "CON-011",
        "CON-012"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-013",
      "slug": "con-013",
      "title": "Subscriber attributes must agree with the message bodies supplied",
      "statement": "A campaign must not declare a subscriber attribute that its supplied opt-in, STOP and HELP message bodies contradict.",
      "rationale": "The attributes and the reply bodies are two accounts of the same programme, and a reviewer reads them together: declaring that you honour opt-outs while supplying no STOP reply says the declaration was ticked rather than meant. The contradiction usually comes from an integration that sets the flags from a template and leaves the bodies to a human who never filled them in, so the submitter believes both halves are done.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign attributes vs optin/optout/help message bodies",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30887",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the reply body for every attribute you declared true, and turn the attribute on for every body you supplied. Done when each of the three pairs agrees: opt-out declared and a STOP confirmation written, HELP declared and a HELP reply written.",
      "example": "subscriberOptout: true · optout_message: \"Acme Coffee Rewards: you are unsubscribed and will receive no further messages. Reply HELP for help.\"",
      "pitfalls": [
        "Supplying a STOP or HELP reply while leaving the matching attribute false is the same mismatch in the other direction, and providers reject it just as readily."
      ],
      "notes": "Judges only the pairs where both facts are known: an attribute that has not been declared yet leaves this UNCLEAR rather than failing, because the missing declaration is already CON-010's finding and reporting it twice tells the user nothing new. The opt-in confirmation body is deliberately not required by the true-attribute direction — only keyword programmes must have one, which is CMP-096.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-017",
      "slug": "con-017",
      "title": "An offline opt-in still needs a findable business behind it",
      "statement": "A brand collecting consent entirely offline must still have a verifiable online presence a reviewer can find.",
      "rationale": "With no opt-in URL to inspect, the website is the only independent evidence that the business exists and does what the campaign says. A brand that is invisible online and collecting numbers on paper is indistinguishable, from a reviewer's side, from one that does not exist — which is why offline programmes are held to a higher bar on the brand record rather than a lower one.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "brand website vs offline consent method",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "pos",
          "verbal_live",
          "verbal_ivr",
          "employer_hr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, point of sale, live verbal, IVR and employer / HR.",
      "universal": false,
      "remediation": "Supply a website that names the business, describes what it does, and carries the SMS terms and privacy policy. Where the business genuinely has no site, a complete and consistent listing — a verified profile with the same name, address and phone as the brand record — is what a reviewer will look for instead. Done when the campaign can be corroborated without the opt-in surface.",
      "pitfalls": [
        "A social profile alone rarely satisfies this, because the policy pages have nowhere to live on it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-023",
      "slug": "con-023",
      "title": "Evidence URLs must resolve without a login",
      "statement": "A submitted evidence URL must resolve, be publicly reachable, and not require authentication.",
      "rationale": "The most common evidence failure in practice is a cloud-storage link that shows a \"request access\" wall to anyone outside the organisation. The submitter tested it while logged in and saw their own file, so the failure is invisible from their side.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact URL",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the link to \"anyone with the link can view\", or host the file somewhere public, then open it in a private browsing window to confirm.",
      "pitfalls": [
        "Google Drive and Dropbox default to organisation-only access, which looks fine to you and shows a wall to everyone else.",
        "A viewer page is not the file — link the direct image or PDF."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-024",
      "slug": "con-024",
      "title": "The opt-in URL must serve a certificate that verifies",
      "statement": "The opt-in URL must present a valid, verifiable TLS certificate — not expired, self-signed, or issued for another hostname.",
      "rationale": "A crawler that hits a certificate error stops there and reports the opt-in as unverifiable, so the rejection reads as \"we could not see your consent surface\" rather than as a certificate problem. Brands lose days to that translation, because the page loads perfectly in a browser that has been clicking through the warning for months.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in URL TLS chain",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "2103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Renew or reissue the certificate for the exact hostname in the submitted URL, including the www or bare-domain form you actually registered. Test it from outside your network — an internal proxy or a pinned certificate hides the failure from everyone inside. Done when an external SSL checker reports a complete, valid chain.",
      "example": "opt_in_url: https://acmecoffee.com/checkout — certificate valid for acmecoffee.com, chain complete.",
      "pitfalls": [
        "A certificate valid for acmecoffee.com and not for www.acmecoffee.com fails whenever the submitted URL uses the other form, which is the most common version of this.",
        "An expired intermediate still lets most browsers through and fails a strict crawler — the browser test is not the test that matters."
      ],
      "notes": "CRAWLER GAP, now half closed. RuleInput carries per-URL fetch outcomes (`urlFetches`), which WEB-073 and POL-015 read deterministically for the brand and policy hosts. This rule covers the opt-in URL, which is often a third address the crawler is not yet asked to fetch, so it stays judged and returns UNCLEAR until a `urlFetches` entry exists for that URL. Recorded rather than omitted: the obligation is real and the remaining gap is the fetch layer's.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-027",
      "slug": "con-027",
      "title": "The opt-in URL must land on a page that carries the opt-in",
      "statement": "The page at the submitted opt-in URL must contain the opt-in form itself, not merely mention or link to it.",
      "rationale": "A reviewer opens the URL and expects to see the thing being registered. Landing on a homepage, a marketing page, or a page whose signup lives one click further in reads as evidence that could not be produced, and it is rejected without anyone hunting for the real page. Brands submit the wrong URL constantly because the opt-in is a modal or a footer widget and the page they think of as \"the signup page\" is the one that advertises it.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in page DOM",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "2125",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "qr",
          "third_party"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, QR code and third party.",
      "universal": false,
      "remediation": "Submit the URL of the page the form is actually on, opening the modal first if that is where it lives and using the deep link if one exists. Done when loading that URL in a fresh browser shows the phone field and the consent control without any further clicks.",
      "example": "opt_in_url: https://acmecoffee.com/checkout — not https://acmecoffee.com",
      "pitfalls": [
        "A form that only appears after a delay, a scroll, or an exit-intent trigger is not on the page as far as a crawler is concerned — host a permanent version and register that."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-033",
      "slug": "con-033",
      "title": "Opt-in evidence must be the live surface, not a mockup of it",
      "statement": "The artifact must depict the brand's own working opt-in surface — not stock imagery, a design template, or a mockup of a page that does not exist yet.",
      "rationale": "A designed mockup shows what the brand intends to build, and reviewers approve against what exists. When the campaign starts sending, the real page is whatever was actually shipped, and nobody has ever checked it. Honest brands produce these because the compliance step lands before the launch, which is why the fix is usually a date rather than a design change.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in screenshot vs live page",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Capture the page as it renders in a browser today, at the URL you submitted, with your own copy and product names in it. If the surface is genuinely not live yet, say so in the campaign description and register once it is — a mockup submitted without that statement reads as an attempt to pass off a plan as a page.",
      "pitfalls": [
        "A Figma frame and a browser screenshot look similar at a glance; a reviewer spots the missing browser chrome, placeholder copy, and lorem-ipsum product names immediately."
      ],
      "notes": "Marked [INFERRED] in the research: derived from AWS's placeholder-text ban on samples rather than published in these words. Cite the underlying obligation — evidence must depict the live consent surface — if a user pushes back on the rule as stated.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-034",
      "slug": "con-034",
      "title": "The business name on the artifact must match the registered brand",
      "statement": "The business name visible on the consent artifact must match the registered brand legal name or DBA.",
      "rationale": "An artifact showing a different business is the clearest possible evidence that the consent was collected by someone other than the registrant, which is the consent-transfer problem the framework exists to prevent. It also catches the honest version — a screenshot borrowed from a sister brand because it looked similar enough.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact + brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Supply an artifact from the registered brand's own consent surface. If the site trades under a different name, register that name as the DBA so the two match.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-036",
      "slug": "con-036",
      "title": "Platform-hosted evidence must show the end business, not the platform",
      "statement": "Where consent is collected on a platform, agency, or franchisee surface, the evidence must identify the end business the consumer is agreeing to hear from.",
      "rationale": "A consumer looking at a form carrying only the marketing platform's branding cannot tell whose messages they just agreed to, so the consent names nobody and the brand that later sends is not the brand they said yes to. This is the honest version of the consent-transfer problem: the form genuinely is the platform's, so screenshotting it as-is feels like screenshotting the opt-in, and nothing on screen tells you it is not.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact branding",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Twilio",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30506",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "third_party",
          "imported",
          "franchise_agent",
          "chat"
        ]
      },
      "applicabilityText": "Applies when consent was collected by third party, imported list, franchise or agent and chat.",
      "universal": false,
      "remediation": "Re-capture the evidence with the end business's name or logo inside the same frame as the consent control — usually by setting the platform form's header or logo field, or by capturing the branded page the widget is embedded in rather than the bare widget. Done when somebody who has never heard of your platform can name the business from the image alone.",
      "pitfalls": [
        "Naming the brand in the campaign record does not fix the artifact — the reviewer is judging the picture, not the field beside it.",
        "A franchisee form that names only the franchisor fails the same way as an ISV form: the entity on the artifact must be the entity registered."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-038",
      "slug": "con-038",
      "title": "Verbally-collected informational campaigns need the mockup and the messages",
      "statement": "An informational campaign collecting consent by phone must supply both a mockup of the opt-in workflow and the message content that will be sent under it.",
      "rationale": "With no page to crawl and no marketing copy to read, a reviewer assessing a phone opt-in has only two things: how the ask is made and what arrives afterwards. AWS asks for both together because either alone is unfalsifiable — a reasonable script proves nothing if the messages turn out to be promotional, and reasonable messages prove nothing if nobody agreed to them.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact + campaign.sample_messages",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ],
        "excludeUseCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR, and the use case is NOT MARKETING, MIXED and SWEEPSTAKE.",
      "universal": false,
      "remediation": "Attach the workflow mockup — the script page, call flow, or agent screen, whichever the consumer's consent actually passes through — and populate the sample messages with the real notifications you intend to send. Done when the two read as one story: the script promises a category of message and the samples are that category.",
      "example": "Attached: the agent screen showing the consent step and the script read from it. Samples: \"Acme Coffee: your service appointment is confirmed for Tue 9am. Reply STOP to opt out, HELP for help.\"",
      "pitfalls": [
        "Samples that are more promotional than the script promised fail the use-case check as well — the script is what scopes the consent, so it is the samples that have to move."
      ],
      "notes": "The catalog conditions this on an informational use case; the registry has no \"informational\" flag, so it is expressed as an exclusion of the promotional use cases. AWS states it for verbal collection only, which the consent-method tag carries.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-040",
      "slug": "con-040",
      "title": "The consent basis must be an explicit opt-in, not an implied one",
      "statement": "The submission must rest on a consumer act that agreed to messaging — not on an existing relationship, a published number, or a prior transaction.",
      "rationale": "Implied consent is the theory behind most of the numbers that generate complaints: the consumer gave a number to book a table or buy a part, and reads the marketing text that follows as a stranger having got hold of it. Carriers therefore treat \"they are our customers\" as no consent at all, and a submission that argues the relationship rather than describing an act is rejected on the argument itself.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "AWS",
        "Bandwidth",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the moment the consumer agreed — what they ticked, said, signed or texted, and where — and attach the evidence of it. Where no such moment exists, collect fresh consent before registering: an existing customer list cannot be argued into an opt-in. Done when the flow names an act rather than a relationship.",
      "example": "Customers tick an unchecked box reading \"Text me Acme Coffee offers\" at https://acmecoffee.com/checkout before the order is placed; the ticked state, timestamp and disclosure version are stored against the order.",
      "pitfalls": [
        "\"Customers provide their number when booking\" describes collection, not consent — the number arriving is not the same event as agreeing to be texted."
      ],
      "notes": "The catalog classes this VISION on the toll-free path (Bandwidth TFV 1402). Authored as AI_FORM because the implied-consent claim is made in the message flow rather than visible in the artifact, and the artifact half is already judged by CON-ART-002. Bandwidth applies it specifically to toll-free verification; it is left universal because the underlying FCC standard is.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-041",
      "slug": "con-041",
      "title": "The opt-in form must have a phone-number field",
      "statement": "The opt-in form must collect a mobile number through a field of its own.",
      "rationale": "A form with no number field cannot be where SMS consent is collected, whatever its consent text says — so the registration points at a surface that does not do what it claims. The usual cause is honest: the brand registered their newsletter page, where the only field is an email address, because that is where the SMS checkbox was added.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in form phone input",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Ringover"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "2139",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "qr",
          "third_party"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, QR code and third party.",
      "universal": false,
      "remediation": "Add a mobile-number input to the form the consent control sits on, and label it as a mobile number rather than a generic \"phone\". Done when a consumer can give you a number and their agreement in the same submission.",
      "pitfalls": [
        "Collecting the number on a later step than the consent control splits the two, and a reviewer looking at the registered page sees consent with nothing to attach it to."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-042",
      "slug": "con-042",
      "title": "The phone field must be optional on a general-purpose form",
      "statement": "On a form that exists for something other than SMS signup, the phone-number field must not be required to submit.",
      "rationale": "A required number on a contact or quote form takes the number as the price of the enquiry, and everything downstream treats that as a collected lead rather than an opt-in. Bandwidth states it flatly — phone numbers cannot be a required field on the website where opt-in is collected — because a number given under compulsion cannot be told apart later from one given willingly.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in form phone field required attribute",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30931",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30923",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "account_settings",
          "third_party"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, account settings and third party.",
      "universal": false,
      "remediation": "Drop the required attribute and the asterisk from the phone input on any form whose purpose is not SMS signup, and confirm the form submits without it. Done when a visitor can send you an enquiry giving only an email address.",
      "pitfalls": [
        "A form whose entire purpose IS SMS signup may require the number — the field is the point of the form, and this rule does not apply to it."
      ],
      "notes": "DIVERGENCE, canonical form taken: Bandwidth forbids a required phone field outright; Telnyx and the website-evidence research allow one provided the SMS consent control is itself optional. The strict-superset rule takes Bandwidth, so this fires wherever the form has another purpose. Where the phone field genuinely must stay required for that purpose, CON-043 and CON-044 are the rules that make it survivable.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-043",
      "slug": "con-043",
      "title": "A mandatory phone field needs its own separate, optional SMS opt-in",
      "statement": "Where the phone field is mandatory for another purpose, an additional and separately optional SMS consent control is required.",
      "rationale": "A phone number collected for delivery coordination is not consent to be marketed to, so the two must be separable. Businesses frequently reason that because the number is required, the consent is implied — which is precisely the inference the rule exists to block.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep the phone field mandatory for its operational purpose, and add a separate optional checkbox for messaging consent beside it.",
      "example": "Phone number (required — for delivery updates)\n☐ Also text me promotional offers from Acme Coffee (optional).",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-044",
      "slug": "con-044",
      "title": "A required phone field must carry an SMS disclaimer beside it",
      "statement": "Where the phone-number field is mandatory, the text next to it must say whether the number will be used for text messages.",
      "rationale": "Somebody typing a mandatory number needs to know at that moment what it will be used for — a number given for a delivery call is not a number given for marketing texts, and the difference is decided by what was written beside the box. This is the disclosure half of the required-field problem: even a form that legitimately needs the number owes the consumer that sentence.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "text adjacent to the phone field",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx",
        "Zoom (ISV)"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "qr",
          "third_party"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, QR code and third party.",
      "universal": false,
      "remediation": "Put a one-line disclaimer directly under the phone input saying what the number is for and whether texts are part of it. Done when the sentence sits within the field's own block rather than in a disclosure lower down the page.",
      "example": "Mobile number (required — we text delivery updates to this number). Marketing texts are separate and optional; see the box below.",
      "pitfalls": [
        "A disclaimer that appears only after the field is focused or filled is not present at the moment the consumer decides to type."
      ],
      "notes": "Distinct from CON-043, which requires a separate optional CONTROL where the number is mandatory. This rule is about the TEXT beside the field, and a form can satisfy one and fail the other.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-045",
      "slug": "con-045",
      "title": "The opt-in page must be reachable without logging in",
      "statement": "The registered opt-in page must load for an anonymous visitor, not sit behind a login, paywall, or member area.",
      "rationale": "Reviewers verify anonymously, so a page that renders perfectly for you and shows a sign-in wall to them is, from their side, no evidence at all. It is a silent failure: the submitter tests the link while logged in, sees their own form, and cannot reproduce the rejection.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in page auth state",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "804",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30922",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social and email.",
      "universal": false,
      "remediation": "Either move the opt-in to a public page, or keep it gated and supply a hosted screenshot instead, saying in the message flow that the surface is login-gated and the screenshot is attached. Done when you can open the URL in a private browsing window and see the form.",
      "pitfalls": [
        "A preference centre inside a customer account is legitimately gated — that case is served by attaching the screenshot, not by making the account public."
      ],
      "notes": "Where the surface genuinely is gated, CMP-065 (hosted evidence) and CMP-127 (declare it) are the rules that carry the registration, and this one is discharged by that route rather than failed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-048",
      "slug": "con-048",
      "title": "Every page that collects a number needs consent language",
      "statement": "SMS opt-in language and a consent control must appear on every page of the site that collects a mobile number, not only the page named in the registration.",
      "rationale": "Numbers reach the sending list from every form on the site — contact, booking, quote, donation, callback — and the ones collected on unlabelled forms carry no consent at all. Reviewers crawl for phone inputs rather than trusting the registered URL, which makes this the single highest-yield finding in the corpus, and the one brands are most surprised by: they fixed the signup page and never thought of the contact form.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "all pages containing a phone input",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx",
        "Commio",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "803",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Inventory every form on the site that takes a phone number, and add the consent control and disclosure to each one — or remove the phone field from the forms that do not need it, which is usually the cheaper fix. Done when a site-wide search for phone inputs returns nothing without a consent block beside it.",
      "pitfalls": [
        "Forms embedded from a booking or scheduling vendor are the ones most often missed, because nobody thinks of them as pages on the site."
      ],
      "notes": "Needs a whole-site crawl for phone inputs. The current crawler fetches the registered page only, so this rule will return UNCLEAR in production until that lands — recorded here rather than omitted, because the requirement is real and the gap is the crawler's.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-049",
      "slug": "con-049",
      "title": "Secondary collection pages owe the full disclosure, not a token line",
      "statement": "The consent language on a secondary phone-collecting page must carry the opt-out instruction and the policy links, not merely mention texting.",
      "rationale": "Having added a line to the contact form, brands stop there — so the secondary pages end up with \"we may text you\" and none of the elements that make an opt-in valid. A consumer who consents on that page has been given materially less than one who consented on the signup page, and their consent is correspondingly weaker.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "secondary phone-collecting pages",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "803",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "861",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "dependsOn": [
        "CON-048"
      ],
      "remediation": "Reuse the same consent block on every form rather than writing a shorter one per page — one snippet, included everywhere, is both less work and the only way the pages stay in step when the wording changes. Done when the contact form shows the same disclosure as the signup page.",
      "example": "Include the same block used at checkout: consent sentence, \"Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe, HELP for help.\", and links to the Acme Coffee Terms of Service and Privacy Policy.",
      "notes": "Depends on CON-048: until we know which secondary pages exist, there is nothing to judge the wording of. Subject to the same whole-site crawl limitation.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-059",
      "slug": "con-059",
      "title": "The consent control label must name the programme, not just contact",
      "statement": "The label on the consent control must say what the consumer is subscribing to — a bare \"Text me\" or \"Contact me\" is not specific enough.",
      "rationale": "The label is the only sentence many consumers read before ticking, so it is the whole of what they agreed to. \"We may contact you\" agrees to nothing in particular, which means the brand cannot later show that recurring marketing was within it — and Bandwidth reads the same defect as a failure to name the specific message programme.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent checkbox label text",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx",
        "Bandwidth",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "611",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30513",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Rewrite the label to name the brand and what the messages are about. Done when the label alone, with the rest of the page covered up, tells a stranger who will text them and why.",
      "example": "☐ Text me Acme Coffee offers and rewards updates.",
      "pitfalls": [
        "\"I agree to receive SMS messages\" names the channel but not the programme, and Bandwidth rejects it for the same reason as \"Contact me\"."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-060",
      "slug": "con-060",
      "title": "The opt-in language must actually say SMS or text message",
      "statement": "The consent wording must name text messaging explicitly rather than referring generally to being contacted.",
      "rationale": "Consent runs to a channel. Agreeing to be \"contacted\" or to \"receive updates\" does not agree to texts, and the carriers reading that wording cannot tell whether the consumer expected a phone call, an email, or nothing at all. Twilio rejects it under a code whose whole description is that the opt-in flow does not explicitly mention SMS — it is a wording defect with a one-word fix that brands miss because they know what they meant.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30513",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the words \"text messages\" or \"SMS\" into the consent sentence itself. Done when the sentence names the channel without relying on the page around it — a heading saying \"SMS signup\" above a box that says \"keep me updated\" does not carry it.",
      "example": "I agree to receive recurring automated marketing text messages (SMS) from Acme Coffee.",
      "pitfalls": [
        "An email opt-in reused for SMS is the usual source: the wording was written for a newsletter and the channel word was never added."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-063",
      "slug": "con-063",
      "title": "Labelling the consent control \"optional\" pre-empts the gating rejection",
      "statement": "The messaging consent control should be labelled optional where the form around it collects anything required.",
      "rationale": "Reviewers decide whether a control is required by looking at it, not by submitting the form, so a consent box that sits in a column of asterisked fields reads as required even when it is not. The word \"optional\" is what Twilio itself offers as the fix for that rejection, which makes it the cheapest way to avoid an argument you would otherwise win slowly.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent checkbox label",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30923",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Add \"(optional)\" to the consent control's label, and remove any asterisk or required marker near it. Done when the label says optional in words rather than relying on the absence of an asterisk.",
      "example": "☐ Text me Acme Coffee offers and rewards updates (optional).",
      "notes": "Twilio documents this as a solution to error 30923 rather than as an independent requirement, so it is graded MEDIUM and worded as a recommendation. The requirement it protects — that consent is genuinely not required — is CON-NOT-GATED.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-064",
      "slug": "con-064",
      "title": "A kiosk or terminal must not pre-fill the consumer's number",
      "statement": "A payment terminal or kiosk collecting messaging consent must require the consumer to enter their number, not present one already filled in.",
      "rationale": "A number the terminal already knows — from the card, the loyalty account, or the last transaction — turns the consent step into a confirmation of something the consumer never supplied for this purpose. They tap Yes to a number on screen without deciding to hand it over, and the resulting record cannot show that they did.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "kiosk consent screen",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30925",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by point of sale.",
      "universal": false,
      "remediation": "Configure the terminal to present an empty number field at the consent step, even where the number is already on the account. Done when the screenshot shows a blank field and a keypad rather than a masked number and a Confirm button.",
      "pitfalls": [
        "Masking the pre-filled number does not fix it — the consumer still did not type it, and a reviewer reads the mask as evidence the number came from somewhere else."
      ],
      "notes": "Sourced from vendor guidance rather than published carrier text; the underlying obligation is that consent must be an affirmative act by the consumer, which the pre-filled field removes. Cite that if a user pushes back on the rule as stated.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-070",
      "slug": "con-070",
      "title": "Declining messages must not degrade the service afterwards",
      "statement": "A consumer who declines SMS at signup must retain the same access to the underlying service as one who accepted.",
      "rationale": "Consent that is free at the form and expensive afterwards is not free. Where declining quietly costs the customer their booking reminders, their delivery window or their account alerts, the pressure to accept is real even though every control on the page was optional — and the consumer discovers it only after they have chosen.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "post-signup service access",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "FCC"
      ],
      "applicability": {
        "excludeUseCases": [
          "TWO_FACTOR_AUTH",
          "SECURITY_ALERT",
          "FRAUD_ALERT"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT TWO_FACTOR_AUTH, SECURITY_ALERT and FRAUD_ALERT.",
      "universal": false,
      "remediation": "Walk the journey yourself with the box unticked: create the account, place the order, make the booking, and check that everything a subscriber would receive still reaches you by another channel. Fix whatever only arrives by text. Done when the two journeys differ in the channel and nothing else.",
      "notes": "DETECTABLE ONLY BY THE CUSTOMER. Nothing we can crawl shows what happens to an account after signup, so this is surfaced as a warning with the test the user must run themselves: sign up twice, once declining, and compare what the declining account can do and receive. Twilio applies it as the post-signup half of the same standard CON-069 tests at the form.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you walked the signup yourself with the SMS box left unticked, and confirmed the account still gets everything a subscriber gets, by some other channel?",
        "howToCheck": [
          "Create two accounts, place two orders or make two bookings: one accepting SMS, one declining.",
          "Compare what each receives and can do — confirmations, reminders, delivery windows, account alerts, support access.",
          "Anything the declining account never receives at all is the defect. Move it to email or in-app."
        ],
        "failureLooksLike": "The declining customer gets no appointment reminder, because reminders were only ever built as texts. Every control on the form was optional, and declining still cost them the service."
      }
    },
    {
      "id": "CON-071",
      "slug": "con-071",
      "title": "The decline path must be as usable as the accept path",
      "statement": "The control that lets a consumer skip or decline messaging must not be disabled, greyed out, or hidden.",
      "rationale": "A greyed-out \"No thanks\" is a refusal the interface has already made for the consumer, and it produces opt-ins that look voluntary in the data and were not. Design teams reach for the muted style to steer people toward the primary action without realising that here the primary action is a legal consent, where steering is the violation.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "decline/skip control rendering",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "FCC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30931",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "excludeConsentMethods": [
          "verbal_live",
          "verbal_ivr",
          "keyword",
          "qr",
          "imported"
        ]
      },
      "applicabilityText": "Applies when consent was NOT collected by live verbal, IVR, text-to-join keyword, QR code and imported list.",
      "universal": false,
      "remediation": "Render the decline control enabled, at a legible contrast, and in the same visual weight class as the accept control. Keep it in the same place on the screen — a \"No thanks\" moved below the fold is hidden even when it is styled correctly.",
      "pitfalls": [
        "Making the decline control a plain text link beside a large button is the usual compromise, and it is normally accepted; making it grey-on-grey or 10px is not."
      ],
      "notes": "Excluded from the methods where declining means simply not acting — a caller who says no, a consumer who never texts the keyword, a code nobody scans. There is no rendered control to grey out, so the rule would be asking a question the registration cannot answer. The catalog states the same carve-out as CON-069's method list.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-072",
      "slug": "con-072",
      "title": "An alternative to SMS must exist where the service needs to reach the consumer",
      "statement": "A programme must not make receiving text messages the only way to sign up or to be served, with no other channel offered.",
      "rationale": "A consumer who must accept texts because the confirmation, the code or the receipt arrives no other way has not chosen SMS — they have chosen the service. The defect is invisible to the usual checks because there is often no consent control to mark optional: the coercion lives in the product design rather than in the form, and the brand reads their own flow as \"we just text people their booking\".",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "signup flow alternatives",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "FCC",
        "Twilio"
      ],
      "applicability": {
        "excludeUseCases": [
          "TWO_FACTOR_AUTH",
          "SECURITY_ALERT",
          "FRAUD_ALERT"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT TWO_FACTOR_AUTH, SECURITY_ALERT and FRAUD_ALERT.",
      "universal": false,
      "remediation": "Offer at least one non-SMS route to the same outcome — email the confirmation, show it on screen, or let the customer pick a channel — and say so where the number is collected. Done when a customer who never gives a mobile number can still complete the signup and receive whatever the text would have carried.",
      "pitfalls": [
        "Offering an alternative that is worse on purpose (\"email delivery may take up to 5 days\") is the same coercion with a fig leaf, and reads that way to a reviewer.",
        "A 2FA or fraud-alert programme genuinely has no alternative channel — those use cases are excluded from this rule rather than argued out of it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-074",
      "slug": "con-074",
      "title": "The \"not a condition of purchase\" claim must be true in practice",
      "statement": "Where the disclosure says consent is not a condition of purchase, the form or checkout must actually complete with the SMS box unticked.",
      "rationale": "Ranked #7 most-missed. The sentence is necessary but not sufficient: reviewers test the behaviour, and a form that validates the consent box as required contradicts its own disclosure. Passing the wording check while failing the behaviour is the classic false green.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface behaviour",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove required-field validation from the consent control and submit the form with it unticked to confirm it completes. Fix the form, not the sentence.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-075",
      "slug": "con-075",
      "title": "A promotion must be open to people who do not join the SMS programme",
      "statement": "Participation in a promotion, loyalty scheme or offer must not require the consumer to be enrolled in text messaging.",
      "rationale": "T-Mobile puts it flatly: participation in text promotions should never be a requirement. When the only way to get the member price is to be on the list, the enrolment is the price of the discount rather than a decision about messaging, and the carrier reads the resulting numbers as coerced. Brands arrive here by building the offer around the channel — the promotion genuinely runs over SMS — without noticing they have made the channel compulsory.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "promotion terms; CTA copy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "FCC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30475",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the promotion a second route in — a web account, an email sign-up, a code at the till — and price it the same. Keep the SMS enrolment as one way to participate rather than the gate. Done when the terms describe SMS as optional and a non-subscriber can claim the same offer.",
      "example": "Join Acme Coffee Rewards online, in store, or by text. Members get 10% off however they joined; text alerts are optional and you can reply STOP at any time.",
      "pitfalls": [
        "An SMS-only tier that is materially better than every other tier is a requirement in practice, whatever the terms say."
      ],
      "notes": "Distinct from CON-076: there the prize is what buys the opt-in, here the opt-in is what buys the promotion. They fail in opposite directions and are fixed differently — 076 by decoupling the entry, this one by opening a second route in.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-076",
      "slug": "con-076",
      "title": "Opt-in must not be bought with a prize or giveaway entry",
      "statement": "A messaging opt-in must not be offered in exchange for entry to a prize draw, giveaway, raffle or contest.",
      "rationale": "An opt-in given to enter a draw says nothing about wanting the messages, so the list that results behaves like a purchased one: high opt-out rates, high complaint rates, and recipients who do not recognise the sender by the second send. The research names this the highest-frequency trap for legitimate brands, because \"enter to win — text WIN to join\" is a normal-looking retail promotion that nobody involved thinks of as a compliance decision.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in offer copy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "CTIA",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30945",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Separate the two decisions: let people enter the draw without joining the list, and offer the messaging opt-in beside it as its own optional control with its own disclosure. Done when the entry form completes with the SMS box unticked and the prize is never described as what the opt-in earns.",
      "example": "☐ Enter me in the monthly Acme Coffee hamper draw.\n☐ Separately, text me Acme Coffee offers. Consent is not a condition of entry. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "pitfalls": [
        "Making the draw entry the reward for opting in and the opt-in optional \"in theory\" still fails — the question is what the copy offers, not what the checkbox allows.",
        "A genuine sweepstakes can be registered, under the sweepstakes use case with official rules; what cannot be done is paying for opt-ins with entries."
      ],
      "notes": "Twilio 30945 is non-remediable at that provider — the campaign cannot be resubmitted once rejected for it — which is why catching it before submission matters more here than for most rules. Whether contest content in the samples needs the sweepstakes use case is MSG-155, not this rule.",
      "catalogIds": [
        "MSG-208"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-078",
      "slug": "con-078",
      "title": "Printed and in-store consent must not gate the transaction",
      "statement": "Paper forms, till screens and printed advertising must not make SMS consent a condition of the purchase, the discount, or completing the form.",
      "rationale": "The counter is where consent is most easily coerced and least easily evidenced: a customer being asked in front of a queue for their number to \"finish the transaction\" says yes to get out of the shop. Nothing about that is legible afterwards, which is exactly why carriers judge the printed artwork and the terminal screen rather than the brand's account of what staff say.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "paper form copy; POS screen; print CTA artwork",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "paper_consent_form",
      "authorities": [
        "FCC",
        "T-Mobile",
        "Klaviyo"
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "pos",
          "employer_hr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, point of sale and employer / HR.",
      "universal": false,
      "remediation": "Reprint the form or artwork so the messaging tick box sits apart from anything required, carrying \"Consent is not a condition of any purchase.\" Change the till prompt so staff can complete the sale without the number, and brief them that \"skip\" is a normal answer. Done when a customer who declines gets the same price and the same receipt.",
      "pitfalls": [
        "Artwork reading \"Text JOIN to 55512 for 20% off\" makes the discount conditional on the opt-in even where the paper form beside it is clean — the print is the CTA a reviewer judges.",
        "A required phone line on an intake or warranty form gates it just as effectively as a tick box does, because staff will not hand back an incomplete form."
      ],
      "catalogIds": [
        "CON-077",
        "CON-079"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-080",
      "slug": "con-080",
      "title": "The disclosure must name the programme the consumer is joining",
      "statement": "The point-of-consent disclosure must carry a programme or product description — what this particular messaging programme is.",
      "rationale": "CTIA lists the programme description first among the call-to-action elements because it is what the consumer will recognise later: the name in the confirmation text, in the HELP reply, and on the unsubscribe page. Without it a consumer who joined three programmes from one brand cannot tell which one is texting them, and the opt-out they send lands against the wrong list.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "TCR",
        "AWS",
        "Bandwidth",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the programme in the consent sentence and use that same name in the opt-in confirmation, the HELP reply and the terms. Where the programme has no name, describe the product instead — \"coffee subscription updates\" is a description; \"updates\" is not. Done when the same words appear at the point of consent and in the first message that arrives.",
      "example": "☐ Join Acme Coffee Rewards: text me offers and rewards balance updates.",
      "pitfalls": [
        "Naming the programme only in the page heading leaves the consent sentence generic, and the sentence is what a reviewer quotes."
      ],
      "notes": "Distinct from CON-DISC-PURPOSE, which asks for the message CATEGORY (marketing, reminders, order updates). This asks which programme — CTIA MPBP §5.1.1(1) treats them as separate elements, and a disclosure can name a category without naming a programme.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-088",
      "slug": "con-088",
      "title": "Customer-care contact must be reachable from the point of consent",
      "statement": "The call-to-action must give the consumer a way to reach a person — a support email, phone number, or help page.",
      "rationale": "A consumer who wants to ask a question before agreeing, or to complain afterwards, needs somewhere that is not the messaging channel itself. Where there is nowhere, the complaint goes to the carrier instead, and carrier complaints are the input that throttles a campaign. CTIA lists it as a call-to-action element for that reason rather than as a courtesy.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a support email or phone number to the consent block, or link a contact page from inside it. Use the same contact that your HELP auto-reply gives, so the two agree. Done when a consumer can reach you without having joined the programme first.",
      "example": "Questions? Email support@acmecoffee.com or call 415-555-0134. Reply STOP to unsubscribe, HELP for help.",
      "pitfalls": [
        "Pointing only at the HELP keyword is circular for someone deciding whether to opt in — they cannot text HELP to a programme they have not joined."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-090",
      "slug": "con-090",
      "title": "The disclosure should say which number the messages come from",
      "statement": "The call-to-action should state the phone number or short code the programme will send from.",
      "rationale": "An unrecognised number is the most common reason a legitimate message gets reported as spam. Telling the consumer at signup which number to expect is what lets them recognise the first message instead of blocking it, and it is the cheapest protection a programme has against its own delivery rates.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text + campaign number set",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sending number or short code to the consent block, and update it if you change numbers. Where you send from a pool, say so and name the range or the short code rather than listing every number. Done when the consumer knows what will appear on their screen.",
      "example": "Messages come from short code 55512. Msg frequency varies. Msg & data rates may apply.",
      "pitfalls": [
        "Publishing a number you later migrate away from is worse than publishing none — treat the disclosure as something to keep current, not to set once."
      ],
      "notes": "Graded MEDIUM, matching the catalog. CTIA lists it as element (2) of the call to action but no provider rejects on it alone, so it is surfaced as an improvement rather than a blocker.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-093",
      "slug": "con-093",
      "title": "The policy links must say which document they open",
      "statement": "The links to the Privacy Policy and the Terms & Conditions must be labelled as those documents, and the pages they open must title themselves the same way.",
      "rationale": "A consumer deciding whether to read the terms needs to know that is what the link is. \"Learn more\", \"details\", or a bare \"here\" tells them nothing, and a reviewer checking whether the required links are present cannot confirm it either. The document side matters for the same reason: a page reachable from a link labelled Privacy Policy that never calls itself one is not obviously the policy at all.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "link text and document titles of the policy links",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "excludeConsentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was NOT collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Use the document names as the link text — \"Privacy Policy\" and \"Terms of Service\" — and make sure each page carries the matching title as a heading. Done when both the link and the page it opens use the same name.",
      "example": "By checking this box you agree to Acme Coffee's <a href=\"/sms-terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.",
      "pitfalls": [
        "Bundling both documents behind one link labelled \"our policies\" fails even when the destination contains both, because the consumer cannot tell what they are agreeing to before they click."
      ],
      "notes": "Excluded from the verbal methods, which have no link text to label — a script names the policy aloud instead, which CON-VERBAL-ELEMENTS already checks as its eighth element.",
      "catalogIds": [
        "POL-032",
        "POL-120"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-094",
      "slug": "con-094",
      "title": "The policy links at the opt-in must open real policy pages",
      "statement": "The Terms and Privacy Policy links beside the consent control must resolve to live pages carrying those documents — not 404s, placeholders, or unfinished templates.",
      "rationale": "A dead policy link is worse than a missing one: the consent block claims the documents exist and the reviewer proves they do not, which puts the whole submission's accuracy in question rather than just the link. It happens most on sites where the policies were generated during a rebuild and the routes changed afterwards.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "policy links on the opt-in surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open both links from a private browsing window and confirm each returns a real, finished document. Fix the routes rather than the link text, and check for template markers — a generated policy that still says {{COMPANY_NAME}} resolves fine and fails just as hard. Done when both links return a page that names your business.",
      "pitfalls": [
        "A link that redirects to the homepage rather than 404ing looks healthy to a link checker and is treated as a missing policy by a reviewer."
      ],
      "notes": "Partially settleable today: where the policy text has been fetched, this can be judged from the content. Where the fetch failed, the registry carries no HTTP status, so the rule returns UNCLEAR rather than assuming a 404 — the same crawler gap CON-024 records.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-095",
      "slug": "con-095",
      "title": "The no-sharing statement must be reachable from the consent point",
      "statement": "The disclosure, or the privacy policy it links to, must state that mobile information is not shared with third parties for marketing purposes.",
      "rationale": "This is the single clause carriers care most about, because number-sharing is the mechanism behind most of the traffic 10DLC exists to suppress. It has to be findable from the opt-in rather than buried, since it is the promise the consumer is relying on when they hand over the number — and it is the clause free policy generators are least likely to include.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text; privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Telnyx",
        "Bandwidth",
        "Twilio",
        "AWS",
        "Sinch",
        "Infobip",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "805",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence \"Mobile information will not be sold or shared with third parties for promotional or marketing purposes.\" to the privacy policy, and make sure the opt-in links to that policy. \"Sold\" alone is not enough — Telnyx requires both words, because sharing without payment is the case that actually happens.",
      "example": "We will not sell or share your mobile information with third parties for promotional or marketing purposes. Text messaging originator opt-in data is not shared with any third parties.",
      "pitfalls": [
        "A general \"we do not sell your personal data\" clause does not satisfy this — the clause must name mobile information or phone numbers specifically."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-096",
      "slug": "con-096",
      "title": "Nothing in the flow may contradict the no-sharing promise",
      "statement": "No part of the opt-in surface or its linked policy may indicate that opt-in data is shared with third parties.",
      "rationale": "A policy routinely carries the required no-sharing sentence in its SMS section while a general data-sharing clause elsewhere says the opposite, and reviewers read the whole document and treat the permissive clause as controlling. A checker that only looks for the good sentence therefore passes a policy that will be rejected — which is worse than not checking, because the brand submits believing it is covered.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in surface + linked privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS",
        "Twilio",
        "GoHighLevel"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "dependsOn": [
        "CON-095"
      ],
      "remediation": "Find the permissive clause and carve mobile data out of it explicitly, rather than adding a second sentence that contradicts it. Done when the sharing section itself says mobile information is excluded — a reviewer reading only that section reaches the right answer.",
      "example": "We may share aggregated, non-identifying data with partners. This sharing expressly excludes mobile phone numbers and SMS opt-in data, which are never shared with third parties for marketing purposes.",
      "pitfalls": [
        "A cookie or advertising-partner section is where this usually hides — the SMS section is clean and the marketing section three headings earlier is not."
      ],
      "notes": "The inverse trap, on the consent surface rather than the policy page. Depends on CON-095: until the no-sharing statement is located, there is nothing for a contradiction to contradict. POL-064 states the same trap for the policy document read on its own.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-099",
      "slug": "con-099",
      "title": "The opt-in and its advertising must not mislead",
      "statement": "The consent disclosure and the programme advertising must not misrepresent what the programme is, what it costs, or what the consumer gets.",
      "rationale": "Deceptive opt-in copy produces subscribers who did not understand what they joined, and they behave exactly like people who never opted in: they complain, they report, and they tell the carrier the message was unsolicited. CTIA bans it at the call to action rather than only in the messages because the deception that matters most happens before consent, where it manufactures the consent itself.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text; programme advertising copy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the copy describe the programme accurately: what arrives, how often, what it costs, and what the consumer has to do. Remove anything implying a benefit the programme does not deliver or a cost it does not carry. Done when someone who reads only the CTA is not surprised by the first message.",
      "example": "Join Acme Coffee Rewards for offers and your rewards balance. Msg frequency varies. Msg & data rates may apply. No purchase necessary to join.",
      "pitfalls": [
        "Describing a paid programme as \"free\" is the specific case with its own rule and its own penalty — CON-101 — but any overstated benefit fails here."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-101",
      "slug": "con-101",
      "title": "\"Free\" must not be used loosely in the call to action",
      "statement": "The word \"free\" must not appear in the CTA except for genuine free-to-end-user programmes; synonyms must be paired with the rates disclosure.",
      "rationale": "Improper use of \"free\" is a standalone audit violation because it contradicts the message-and-data-rates disclosure sitting beside it — the consumer is told the programme is free and that it may cost them money. Synonyms like \"complimentary\" and \"no charge\" carry the same problem.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent disclosure",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove \"free\" from the CTA, or pair it with the rates disclosure so the two statements do not contradict.",
      "example": "Join our complimentary rewards texts. Message and data rates may apply.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-108",
      "slug": "con-108",
      "title": "The disclosure must be on the same page as the phone-number field",
      "statement": "The consent disclosure must appear on the same page, popup, or form as the phone-number field — not on a separate linked page.",
      "rationale": "Consent is given at the moment the number is submitted, so a disclosure the consumer would have to navigate away to read was not part of that decision. This is subtly different from the terms-link rule: here the disclosure text itself is on another page.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move the disclosure text onto the same form as the phone field, immediately beside the consent control.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-109",
      "slug": "con-109",
      "title": "The disclosure must sit next to the button the consumer presses",
      "statement": "The consent language must appear directly beneath the phone field or immediately above the submit control.",
      "rationale": "Consent is given at the instant of submitting, so the words that count are the ones in view at that instant. A disclosure at the top of a long form has scrolled away by the time the consumer acts, which is why the placement is specified as a position relative to the button rather than as presence on the page.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "disclosure position relative to the submit control",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Klaviyo",
        "Infobip",
        "Salesmsg",
        "Quo"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Move the disclosure into the gap between the last input and the submit button. Done when a screenshot cropped to the submit button still contains the whole disclosure.",
      "pitfalls": [
        "Long forms pass on desktop and fail on mobile, where the same block falls a full screen above the button — check the narrow viewport before capturing evidence."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-110",
      "slug": "con-110",
      "title": "The disclosure must be visible without the consumer opening anything",
      "statement": "The consent disclosure must be rendered on the page as it loads — not behind a tooltip, an accordion, a \"read more\" toggle, or pushed out of the main content.",
      "rationale": "A disclosure the consumer has to reveal is one nearly all of them never see, and the law asks what a reasonable consumer would notice rather than what the page technically contains. Design teams collapse it in good faith, because a five-line legal block ruins a checkout — which is why the fix is usually about where the text sits rather than whether anyone wanted to hide it.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "disclosure rendering and DOM position",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Attentive",
        "Twilio",
        "Infobip",
        "FCC"
      ],
      "applicability": {
        "excludeConsentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was NOT collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Render the disclosure expanded by default in the main content, beside the control. Where the block is too long for the layout, shorten the wording rather than collapsing it — every required element fits in about forty words. Done when the text is readable on first paint with nothing clicked.",
      "example": "☐ Text me Acme Coffee offers. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe, HELP for help. Terms · Privacy",
      "pitfalls": [
        "An accordion that is open by default still fails on mobile, where the same component usually ships collapsed."
      ],
      "notes": "Excluded from the verbal methods, which render nothing — a spoken script has no default state to reveal. Paper, POS and printed CTAs stay in scope: fine print on the reverse of a form is the same defect in another medium.",
      "catalogIds": [
        "CON-111"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-113",
      "slug": "con-113",
      "title": "The disclosure must read as a disclosure, not as more marketing copy",
      "statement": "The consent disclosure must be clear and conspicuous, and set apart from the advertising copy and any other disclosures around it.",
      "rationale": "This is the legal standard the specific placement rules exist to serve, and it catches the cases they miss: a disclosure that is present, visible, adjacent, and still lost inside a paragraph of promotional text. A consumer skimming an offer does not parse a sentence about message rates buried in the middle of it, so the words were technically supplied and functionally absent.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "rendered disclosure",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "CTIA",
        "T-Mobile",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the disclosure its own block: its own lines, separated from the promotional copy and from any other agreement text on the page. Done when a reader can point to where the marketing stops and the terms begin.",
      "example": "Get 20% off your first order when you join Acme Coffee Rewards.\n\n☐ Text me Acme Coffee offers. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe, HELP for help.",
      "pitfalls": [
        "Merging the SMS disclosure into the same paragraph as the email and cookie notices makes all three unreadable, and reviewers treat the SMS one as the casualty."
      ],
      "notes": "The standard itself rather than one of its mechanical proxies, so it is deliberately the last resort: where CON-107, CON-109 and CON-110 all pass and the disclosure is still not conspicuous, this is the rule that says so.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-123",
      "slug": "con-123",
      "title": "Every entity allowed to send must be named in the consent",
      "statement": "The consent language must name each business that will send under it, not only the one the consumer is transacting with.",
      "rationale": "AT&T requires consent to explicitly name the entities authorised to send, because the consumer's expectation is set by the name they read. A franchise group, a dealer network or a parent with several trading brands routinely collects once and sends from all of them, and every message from a name the consumer never saw reads to them as a leak of their number.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30506",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List each sending entity by name in the consent sentence, or narrow the programme so only the named brand sends. Where the list is long, name the group and link a page that enumerates the members — the page must be reachable before the consumer agrees. Done when every number that will appear as a sender traces to a name in the disclosure.",
      "example": "By checking this box you agree to receive recurring automated marketing texts from Acme Coffee and from your local Acme Coffee franchise location.",
      "pitfalls": [
        "Naming a parent company the consumer has never heard of instead of the trading name they recognise satisfies the letter and defeats the purpose — name both."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-124",
      "slug": "con-124",
      "title": "The sending entities must be named before consent is captured",
      "statement": "The authorised sending entities must be disclosed before the consumer acts, not on the confirmation screen or in the first message.",
      "rationale": "Consent is given at the moment of the act, so a name disclosed afterwards was not part of what was agreed to. Brands get this wrong in a specific and understandable way: the confirmation page is where they put the full legal detail, because that is the page nobody has to be persuaded by.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "disclosure ordering on the opt-in surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "dependsOn": [
        "CON-123"
      ],
      "remediation": "Move the entity names above the consent control, inside the sentence the consumer is agreeing to. Keep them on the confirmation as well if you like — the requirement is that they appear before the act, not that they appear only there. Done when the names are on screen at the moment the box is ticked.",
      "example": "☐ Text me offers from Acme Coffee and my local Acme Coffee franchise location.\n\n[Join] — the names appear above this button, not on the page it leads to.",
      "notes": "Depends on CON-123: where no entity is named at all, that is CON-123's finding and this rule has no ordering to judge.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-125",
      "slug": "con-125",
      "title": "Consent must not be claimed on behalf of unnamed partners",
      "statement": "The opt-in language must not purport to grant consent to \"our partners\", \"affiliates\", \"third parties\", or a linked partner list.",
      "rationale": "A consumer cannot consent to being messaged by a set of businesses they have not been told about, so this wording grants nothing while looking like it grants everything — and every carrier treats a number sent under it as having no consent behind it. It is standard boilerplate in lead-generation templates, which is how it reaches brands that have no intention of sharing anything.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "CTIA",
        "AWS",
        "Twilio",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete the partner and affiliate wording from the consent sentence and name only the entities that will actually send. If a partner genuinely needs to message these consumers, they must collect their own consent under their own brand. Done when the sentence contains no open-ended category of recipient.",
      "example": "By checking this box you agree to receive recurring automated marketing texts from Acme Coffee. (No partner, affiliate, or third-party clause.)",
      "pitfalls": [
        "Linking a page that lists the partners does not cure it: the consumer would have to read a list to know who they are agreeing to, and reviewers reject the pattern rather than the length of the list."
      ],
      "notes": "Distinct from CON-NO-TRANSFER, which is about where the consent CAME FROM. This rule is about a consent the brand did collect itself, worded to extend to senders it cannot bind. A brand can pass that rule and fail this one on the same page.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-130",
      "slug": "con-130",
      "title": "Each messaging programme needs its own opt-in control",
      "statement": "A single opt-in control must not enrol the consumer into several messaging programmes or use cases at once.",
      "rationale": "One tick enrolling somebody into offers, delivery alerts and service notices gives them no way to keep the one they wanted and drop the two they did not — so the only exit is STOP, and the brand loses the whole relationship over a programme the consumer never asked for. It also makes the consent unprovable per programme, which is what a reviewer is checking.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in surface control set",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Infobip",
        "Bandwidth",
        "CTIA"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Give each programme its own checkbox with its own description, and register each as its own campaign. Done when a consumer can join the rewards texts without joining the service alerts.",
      "example": "☐ Acme Coffee Rewards: offers and rewards balance updates.\n☐ Acme Coffee order updates: delivery and pickup notifications.",
      "pitfalls": [
        "Splitting the controls but sending both programmes from one campaign puts the split back where it started — the campaign registration has to match the control set."
      ],
      "notes": "The surface half of the scope rule. CON-SCOPE-SPLIT covers the specific and most common instance — promotional bundled with transactional — while this rule covers a control that spans several programmes of any kind. Where the only bundling is marketing-plus-transactional, report it there rather than twice.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-132",
      "slug": "con-132",
      "title": "Email or voice consent does not become SMS consent",
      "statement": "Consent collected for email or phone calls must not be treated as consent to text, and an email list must not be migrated to SMS without a fresh opt-in.",
      "rationale": "Consent runs to a channel, and the consumer who gave an address for a newsletter made no decision about their phone. Migrating a list is the single most common way a careful brand acquires numbers it has no consent for, because the numbers were genuinely volunteered and the brand genuinely has a relationship — everything about it feels legitimate except the part that matters.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent scope; list provenance",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth",
        "T-Mobile",
        "Twilio",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Numbers whose only consent was for another channel cannot be messaged under any use case. Re-permission them first: email the list asking them to opt in to texts, and send only to those who complete the SMS opt-in. The email invitation is not the consent — the landing page they submit is.",
      "notes": "HARD_STOP because there is no field that fixes it: the defect is which numbers are on the list, not how the campaign is described. The remediation is a real path, but it happens before registration rather than inside it.",
      "catalogIds": [
        "CON-136"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-134",
      "slug": "con-134",
      "title": "SMS consent needs its own control and its own sentence",
      "statement": "Messaging consent must not share a control or a sentence with email or phone-call consent.",
      "rationale": "One box reading \"yes, contact me by email and text\" produces a record that cannot show which channel the consumer wanted, and the honest answer is usually email — that is the one they were thinking about. Klaviyo warns against collecting both on one surface for the same reason: the combined control converts better precisely because it captures people who did not decide.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "opt-in surface control set; consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Telnyx",
        "Twilio",
        "TextUs",
        "Infobip",
        "Bird",
        "Sakari",
        "Klaviyo"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Split into two controls with two sentences: one for email, one for text. The SMS sentence carries the SMS disclosures; the email one does not need them. Done when a consumer can take the newsletter and decline the texts in one submission.",
      "example": "☐ Email me Acme Coffee news.\n☐ Text me Acme Coffee offers. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe, HELP for help.",
      "pitfalls": [
        "Two checkboxes sharing one sentence above them is the same defect with extra steps — each control needs wording of its own that names its channel."
      ],
      "notes": "Distinct from CON-BOX-BUNDLED, which is about bundling with terms acceptance. Here both halves are genuine consents and the defect is that they are one act. WEB-100 states the same rule for a QR landing page, and CON-133 the wording half.",
      "catalogIds": [
        "CON-133",
        "WEB-100"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-137",
      "slug": "con-137",
      "title": "The consent record must capture the number it authorises",
      "statement": "The consent record must store the specific telephone number the consumer authorised messages to be sent to.",
      "rationale": "The federal definition of written consent turns on the number the signatory authorised, so a record proving somebody consented without recording which number they consented for cannot be matched to the message that was sent. It is the failure that surfaces late and badly: the brand has thousands of consent records and cannot tie any of them to the number in the complaint.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact phone field",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC",
        "SIP.US"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Store the number on the consent record itself, in the form it was given, next to the timestamp and the disclosure version. Done when you can take any number from your sending list and produce the record that authorised it.",
      "example": "Each consent record stores: phone number, timestamp, collection surface, disclosure version shown, and the session or agent identity.",
      "pitfalls": [
        "Storing consent against a customer account rather than a number breaks the moment somebody changes their number, and the old number stays on the sending list with a record that no longer describes it."
      ],
      "notes": "Judged from what the flow and the artifact say about the record, since the record itself is the brand's own system. OPS-205 states the same field requirement on the retained record.",
      "catalogIds": [
        "OPS-205"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-139",
      "slug": "con-139",
      "title": "A business built on acquiring opt-in lists cannot be registered",
      "statement": "A campaign whose business model is acquiring, aggregating or monetising opt-in subscriber lists must not be registered.",
      "rationale": "Where the list IS the product, no amount of documentation fixes the campaign, because the consumers on it agreed to hear from whoever collected them and not from whoever bought them. Carriers name lead generation specifically in their prohibited categories for this reason, and the enforcement history behind 10DLC is largely this business model.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign business model",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AT&T",
        "Bandwidth",
        "Twilio",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A lead-generation or list-acquisition programme cannot be registered under any use case. The only messaging that can be is to consumers who opted in to hear from this brand directly, under this brand's own name.",
      "notes": "Distinct from CON-NO-TRANSFER, which asks where one list came from. This asks what the business does: a retailer that once bought a list has a fixable provenance problem, while a lead vendor has nothing to fix.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-145",
      "slug": "con-145",
      "title": "Consent must come from the consumer, not from a legal process",
      "statement": "Permission to message obtained by court order, or by any other mechanism the consumer did not initiate, is not consent.",
      "rationale": "Consent is a decision the consumer makes, so an order compelling contact is the opposite of one however lawful the order is. Bandwidth is unusually blunt about the consequence: if you can obtain opt-in another way, resubmit — and if you cannot, do not, because the opt-in will never be compliant. That sentence is worth surfacing before submission rather than after.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent provenance declaration",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "2126",
          "remediable": false
        },
        {
          "provider": "Bandwidth/DCA2",
          "code": "714",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Messaging on this basis cannot be registered. Where the same consumers can be asked directly — a form, a call, a signed document — collect that consent and describe it instead. Where they cannot, there is no route to registration and resubmitting will not produce one.",
      "notes": "Bandwidth marks 2126 conditionally remediable: remediable only if consent can be re-obtained lawfully by another method, and not remediable at all otherwise. Recorded as non-remediable because the rule fires on the court-order basis itself, which is the unfixable case.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-146",
      "slug": "con-146",
      "title": "A TCPA exemption is not a substitute for opt-in",
      "statement": "An organisational or governmental exemption from the TCPA does not remove the requirement to obtain an opt-in.",
      "rationale": "Being exempt from a federal statute says nothing about the carriers' own code of conduct, and it is the carriers who decide whether traffic is delivered. Non-profits and public bodies reach this conclusion honestly — their lawyer is right that the TCPA does not reach them — and are then surprised by a rejection that is not about the law at all.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent basis declaration",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "1415",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe and evidence a real opt-in as well as the exemption. The exemption is worth keeping in the submission — it explains your legal position — but it must sit alongside a collection surface, not in place of one. Done when the flow names an act of consent, whatever else it also says.",
      "example": "Riverside Shelter is a 501(c)(3) and relies on the non-profit exemption for TCPA purposes. Supporters additionally opt in at riversideshelter.org/alerts by ticking an unchecked box; screenshot attached.",
      "pitfalls": [
        "Bandwidth is stricter here than the statute, and says so — arguing the law in a resubmission does not change the outcome."
      ],
      "notes": "Flagged in the research as stricter than the law: the exemption is genuine and the carrier requirement is separate. Say that plainly to a user who pushes back, rather than implying their legal analysis is wrong.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-147",
      "slug": "con-147",
      "title": "An exemption relied on must be met in every one of its conditions",
      "statement": "Where a 47 CFR 64.1200(a)(9) exemption is relied on, every condition attached to it must be satisfied — free to the end user, within the message caps, sender identified, and opt-out in each message.",
      "rationale": "These exemptions are narrow and conditional, and failing any single condition collapses the whole thing back to needing consent — retrospectively, for everything already sent. The healthcare cap is the one most often missed: one message per day and three per week combined across voice and text, counted per patient rather than per programme.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "exemption claim",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Go through the conditions for the specific exemption you are claiming and evidence each: that the messages are free to the recipient, that your declared frequency is inside the cap, that every message identifies you, and that every message carries an opt-out. Where any fails, collect consent instead and stop relying on the exemption.",
      "example": "Package-delivery exemption: notifications are free to the recipient, one per shipment, identify Acme Coffee by name, and carry \"Reply STOP to opt out\" — honoured within six business days.",
      "pitfalls": [
        "The healthcare cap counts voice calls and texts together, so a practice already making reminder calls is usually over it before the SMS programme sends anything."
      ],
      "notes": "The registry holds no free-to-end-user fact and no per-recipient frequency, so the conditions are pushed into the judgement criteria rather than expressed as applicability, and the rule only fires where the submission itself claims an exemption.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-150",
      "slug": "con-150",
      "title": "A toll-free number must belong to one business",
      "statement": "A single toll-free number must be associated with exactly one business.",
      "rationale": "A number shared across businesses makes every consent on it ambiguous — the consumer agreed to hear from one of them and cannot tell which is texting, and neither can a reviewer tracing a complaint. It is a common shape in franchise groups and agencies, where one number is provisioned centrally and used by several trading entities because that is how the phone system was set up years earlier.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "toll-free number to brand mapping",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Provision a separate toll-free number per business and register each against its own brand. Where a number is already shared, move the other businesses off it before submitting — a verification approved against one brand does not extend to the others using the number.",
      "notes": "NOT VISIBLE TO US. The registry holds no number inventory, so we cannot see which numbers a brand has or who else is sending from them. Surfaced as a warning with the check the user must run themselves: list every business currently sending from the toll-free number on this registration, and confirm the answer is one. Their carrier or messaging platform can produce that list; we cannot.",
      "catalogIds": [
        "OPS-019"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is exactly one business sending from the toll-free number on this registration?",
        "howToCheck": [
          "Ask your carrier or messaging platform for the list of accounts and sub-accounts provisioned against this number.",
          "Read the list as a consumer would: every distinct trading entity on it is a separate business claiming the same number.",
          "Common in franchise groups and agencies, where one number was provisioned centrally years earlier."
        ],
        "failureLooksLike": "Three franchisees share the number their head office bought. A verification approved for one of them does not extend to the other two, and a complaint against any of them lands on all three."
      }
    },
    {
      "id": "CON-151",
      "slug": "con-151",
      "title": "Promotional messaging needs consent in writing, and must say so",
      "statement": "A campaign carrying promotional content must rest on prior express written consent, and the submission must describe that consent as a written record.",
      "rationale": "Marketing by automated text is the case the TCPA requires a writing for, and a writing is what the brand will have to produce if the consent is ever challenged. The reason to check it at registration is that the alternative is discovering years of sends rested on a verbal yes nobody wrote down — at which point every message already delivered is exposed.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent basis; consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC",
        "CTIA",
        "AWS",
        "Bandwidth",
        "Twilio"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Describe the written record: a ticked box with a stored timestamp, a signed form, a captured e-signature. Say where it is retained and what it contains. Where the consent was spoken, move the marketing programme onto a written opt-in before registering it and keep the verbal consent for the informational messages it can carry.",
      "example": "Marketing consent is captured in writing at checkout: the ticked box, the disclosure version shown, the timestamp and the session id are stored against the order and retained for four years.",
      "pitfalls": [
        "An electronic record only counts as the writing once the E-SIGN predicate is met — the consumer must have received the E-SIGN disclosures and agreed electronically, which most checkout flows have never done."
      ],
      "notes": "Oral consent remains adequate for purely informational or transactional programmes, which is why this is tagged to the promotional use cases rather than left universal.",
      "catalogIds": [
        "CON-152"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-156",
      "slug": "con-156",
      "title": "A call recording is not the written consent",
      "statement": "A recording of the consumer agreeing does not satisfy a requirement for consent in writing.",
      "rationale": "E-SIGN gives electronic records the force of a writing but expressly excludes oral recordings, so a recorded yes is strong evidence of consent and is still not the writing the rule asks for. Call centres reach for it in good faith because the recording feels more probative than a tick box — and it is, on every question except the one that decides the case.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact medium",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "E-SIGN Act",
        "FCC"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Keep the recording, and add a writing beside it: send a confirmation link the consumer submits, capture a DTMF keypress, or follow the call with a double opt-in text they reply to. Done when a written record exists for each number, with the recording as corroboration rather than as the consent itself.",
      "example": "Agents capture spoken agreement (call recorded), then send a confirmation text the customer replies YES to; the reply, its timestamp and the number are stored as the written record.",
      "pitfalls": [
        "A transcript of the recording is not a cure — it is a record of speech, and the exclusion is about the consent being oral rather than about it being unwritten down."
      ],
      "notes": "Tagged to the promotional use cases for the same reason as CON-151: informational programmes may rest on oral consent, so the writing requirement does not reach them. A DTMF keypress is a stronger theory than a recording, which CON-IVR-CONFIRM already asks IVR flows for.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-160",
      "slug": "con-160",
      "title": "A marketing programme must say \"marketing\" everywhere it describes itself",
      "statement": "For a marketing campaign, the call to action, the message flow and the campaign description must each state that the messages are marketing or promotional.",
      "rationale": "Reviewers compare the three, and a campaign registered as MARKETING whose consent language only mentions \"updates\" is either mislabelled or collecting consent too narrow for what it will send. Both readings get rejected, and the second is the one that actually happens: the CTA was written for a service programme and the marketing was added later.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text; campaign.message_flow; campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Put the word marketing or promotional into all three: the consent sentence on the surface, the message flow, and the campaign description. Fix the CTA first — the other two are fields you control, and a flow claiming marketing consent that the live page does not collect is the worse defect. Done when all three agree.",
      "example": "CTA: \"…recurring automated marketing text messages from Acme Coffee.\" · Flow: \"Customers opt in to marketing texts at checkout.\" · Description: \"Acme Coffee sends weekly promotional offers…\"",
      "pitfalls": [
        "Adding the word to the two form fields and not to the live page is the version that fails hardest, because the mismatch now proves the consent is narrower than the registration."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-162",
      "slug": "con-162",
      "title": "A chat conversation is not a marketing opt-in",
      "statement": "Consent inferred from a chat widget or an inbound conversation must not be used to justify a marketing use case.",
      "rationale": "Somebody who opened a support chat consented to that conversation, not to a promotional programme. The two get conflated because the chat platform holds a phone number and an apparent agreement to be contacted, so the marketing list builds itself out of support enquiries — which is exactly the mismatch between what was agreed and what arrives that drives complaints.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent basis vs use case",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "consentMethods": [
          "chat"
        ],
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when consent was collected by chat, and the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Either register the programme as customer care, matching what the chat consent actually covers, or add a marketing opt-in inside the chat flow with the full disclosure and record it separately. Done when the use case and the consent describe the same thing.",
      "example": "In-chat: \"Want Acme Coffee offers by text too? Reply YES to opt in — msg frequency varies, msg & data rates may apply, reply STOP to unsubscribe.\" Only repliers join the marketing list.",
      "pitfalls": [
        "A conversational opt-in inside the chat is fine and is the fix; what fails is treating the existence of the conversation as the consent."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-163",
      "slug": "con-163",
      "title": "Keyword consent is bounded by what the advertisement promised",
      "statement": "Consent given by texting a keyword extends only to the programme advertised alongside that keyword.",
      "rationale": "With text-to-join, the advertisement is the whole disclosure — the consumer decided before the first message existed. So a keyword advertised for shipping alerts collects consent for shipping alerts, and the promotional programme it actually enrols people into was never disclosed to anybody who joined.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "keyword advertisement copy vs campaign content",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "keyword_cta_copy",
      "authorities": [
        "Klaviyo",
        "CTIA"
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Make the advertised copy describe everything the keyword enrols the consumer into, or split the programmes and advertise a keyword for each. Done when the samples contain nothing a reader of the advertisement would not have expected.",
      "example": "Text JOIN to 55512 for Acme Coffee offers and rewards updates. Msg frequency varies. Msg & data rates may apply. Reply STOP to cancel, HELP for help.",
      "pitfalls": [
        "Running one keyword across several campaigns is the usual cause, and it fails here even when each campaign is individually compliant."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-164",
      "slug": "con-164",
      "title": "Text-to-join marketing still owes the written-consent elements",
      "statement": "A marketing text-to-join CTA must carry the federal written-consent elements — the ATDS disclosure, the not-a-condition statement, and the number being authorised.",
      "rationale": "An inbound keyword is unambiguous express consent, and that is not the same as express WRITTEN consent, which is what automated marketing requires. The gap is easy to miss precisely because the keyword feels stronger than a checkbox: the consumer actively texted you, so nobody thinks to check whether the advertisement carried the elements a form would have had to.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "keyword CTA copy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "keyword_cta_copy",
      "authorities": [
        "FCC"
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ],
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword, and the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Add the missing elements to the advertised copy: that the messages are automated and recurring, that consent is not a condition of purchase, and that the number texted from is the number being authorised. Done when the advertisement would satisfy the written-consent test if it were a form.",
      "example": "Text JOIN to 55512 to get recurring automated marketing texts from Acme Coffee at the number you text from. Consent is not a condition of purchase. Msg frequency varies. Msg & data rates may apply. Reply STOP to cancel, HELP for help.",
      "pitfalls": [
        "The number element is the one keyword programmes almost always miss, and it is the easiest to add: the number they text from is the number they are authorising, and saying so costs eight words."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-166",
      "slug": "con-166",
      "title": "A donation programme must disclose that it solicits donations",
      "statement": "Where the programme solicits donations, the point of consent must say so.",
      "rationale": "A supporter who signed up for news about a charity's work did not agree to be asked for money, and the ask is the message most likely to be reported when it was not expected. Saying it at the outset costs a clause and converts the complaint into an opt-out that never happens.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Infobip",
        "Bandwidth",
        "Telnyx"
      ],
      "applicability": {
        "useCases": [
          "CHARITY",
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY and POLITICAL.",
      "universal": false,
      "remediation": "Add donation solicitation to the message-type disclosure at the point of consent, and use the same wording in the campaign description. Done when the consent sentence names fundraising alongside whatever else the programme sends.",
      "example": "☐ Text me Riverside Shelter updates and occasional donation requests. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "pitfalls": [
        "A charity that sends both service information and appeals owes separate opt-ins for the two, so adding the donation clause to a single combined control fixes this rule and fails CON-SCOPE-SPLIT."
      ],
      "catalogIds": [
        "MSG-269"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-AGE-GATE",
      "slug": "con-age-gate",
      "title": "Age-gated programs require real age verification at opt-in",
      "statement": "A campaign declaring age-gated content must verify age at the point of consent, not merely ask for self-attestation.",
      "rationale": "Robust age verification means electronic confirmation of age and identity, which in practice means collecting a full date of birth. The carrier discretion to waive age gating was removed in SCMH v1.8, so a self-attest tick box that used to be tolerated now fails, and the underlying content categories carry per-message violation fees.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "TCR"
      ],
      "applicability": {
        "attributes": {
          "ageGated": true
        }
      },
      "applicabilityText": "Applies when `ageGated` is true.",
      "universal": false,
      "remediation": "Collect a full date of birth (MM/DD/YYYY) at opt-in and reject under-age submissions server-side. Replace any \"I am 21 or older\" checkbox — self-attestation is not robust verification and is no longer waivable.",
      "catalogIds": [
        "WEB-108",
        "WEB-113"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-ART-001",
      "slug": "con-art-001",
      "title": "An artifact proving consent collection must be attached",
      "statement": "The opt-in / message flow must reference a durable artifact evidencing how consent is collected — a screenshot of the opt-in screen, a PDF or scan of the paper form, the verbal script, or a capture of the QR landing page.",
      "rationale": "Every provider reject list is dense with \"opt-in could not be verified\" reasons, and a reviewer who cannot see your consent surface has no way to distinguish a compliant one from a claim. CTIA MPBP 5.1.2 separately requires you to retain \"a capture of the experience\" used to secure consent — so the artifact is something you are obliged to hold anyway. Producing it at registration converts the single most common soft rejection into a solved problem.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Ekas (product policy)",
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Attach proof of your opt-in and reference it in the message flow. For a web form, a full-width screenshot of the form with the consent text visible. For phone consent, the script your agents read. For paper, a scan of the signed form. For QR, a capture of the landing page. Host it somewhere publicly reachable — a reviewer must be able to open it without logging in.",
      "example": "Consent is collected at https://acmecoffee.com/signup via an unchecked checkbox. Screenshot of the live form: https://cdn.acmecoffee.com/compliance/optin-2026-07.png",
      "pitfalls": [
        "A Google Drive or Dropbox share link that shows a \"request access\" wall is treated as no evidence at all.",
        "A viewer page is not an image — link the file directly so it opens as a picture."
      ],
      "notes": "STRICTER THAN THE BASELINE BY DESIGN, per product policy. TCR does not universally demand an artifact; we do. That is also why CON-037 (a mockup or script for 2FA and OTP campaigns) and CON-039 (an opt-in description or screenshot on toll-free verification) are aliased here rather than written separately: both are this requirement narrowed to one path, and a rule that already demands the artifact from everyone discharges them outright.",
      "catalogIds": [
        "CON-018",
        "CON-037",
        "CON-039"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-ART-002",
      "slug": "con-art-002",
      "title": "The consent artifact must actually show a consent surface",
      "statement": "The attached artifact must depict the point of consent collection, not an unrelated page, logo, or product screen.",
      "rationale": "The most common evidence failure is not a missing screenshot but the wrong one — a homepage, a logo, or a dashboard. Reviewers read that as an attempt to satisfy the requirement without meeting it, and it costs more credibility than attaching nothing. Catching it before submission avoids a rejection that is embarrassing as well as expensive.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the attachment with an image of the actual screen or form where the customer gives consent. Three things must appear together in one frame: the phone-number field, the control they act on (checkbox, toggle, or signature line), and the disclosure text beside it.",
      "pitfalls": [
        "A screenshot of the confirmation page after signup shows the outcome, not the consent — capture the form itself."
      ],
      "catalogIds": [
        "CON-028"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-ART-003",
      "slug": "con-art-003",
      "title": "The consent artifact must be legible",
      "statement": "The artifact must be readable at the resolution supplied — cropped, blurred, or truncated evidence does not establish consent.",
      "rationale": "A reviewer who cannot read your disclosure treats it as absent, so an illegible screenshot fails the same way a missing disclosure does, while looking to you like the requirement was met. This is a silent failure mode: the brand believes it supplied evidence and cannot understand the rejection.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Re-capture at full width with the disclosure text readable without zooming. Capture the whole consent block rather than cropping tightly to the checkbox, and avoid photographing a screen with a phone — screenshot it directly.",
      "notes": "whenUnreadable is FAIL, against the default, because illegibility IS the defect this rule tests — returning UNCLEAR would make the rule unable to ever fail. Only the user can fix it: they must re-capture the artifact themselves, since we cannot reach their consent surface.",
      "catalogIds": [
        "CMP-121"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-BOX-BUNDLED",
      "slug": "con-box-bundled",
      "title": "Messaging consent must be separate from terms acceptance",
      "statement": "The messaging opt-in must be its own control, not bundled into a general \"I accept the Terms and Privacy Policy\" checkbox.",
      "rationale": "A consumer ticking a box to create an account is agreeing to the account, not to marketing texts. Bundling makes the messaging consent unprovable — you cannot show a reviewer, or a court, that the tick meant yes to SMS specifically.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Split into two adjacent checkboxes: one for accepting the Terms and Privacy Policy, one for agreeing to receive text messages. Only the second one is your consent record.",
      "example": "☐ I agree to the Terms of Service and Privacy Policy.\n☐ Text me: I agree to receive recurring automated marketing text messages from Acme Coffee. Consent is not a condition of any purchase. Msg frequency varies. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "catalogIds": [
        "CON-055"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-BOX-PRESENT",
      "slug": "con-box-present",
      "title": "An explicit affirmative opt-in control must exist",
      "statement": "Consent must be captured by a deliberate affirmative action — a checkbox, toggle, or signature — never inferred from form submission.",
      "rationale": "Express written consent requires an act that means \"yes to messages\" and nothing else. When consent is bundled into pressing Submit, there is no way to distinguish someone who wanted texts from someone who wanted an account, and no way to evidence the difference later.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "FCC",
        "Twilio"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Add a dedicated checkbox the customer must tick, unchecked by default, with the consent disclosure printed beside it. Replace any \"by submitting this form you agree to receive texts\" wording with a real control.",
      "example": "☐ By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "catalogIds": [
        "CON-051",
        "CON-054"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-BOX-UNCHECKED",
      "slug": "con-box-unchecked",
      "title": "The consent control must be unchecked by default",
      "statement": "The opt-in control must not be pre-selected; consent must be a deliberate act.",
      "rationale": "A pre-ticked box collects consent from everyone who did not notice it, which is the definition of consent that was not affirmatively given. It is one of the few defects a reviewer can confirm from a single screenshot, so it is caught reliably and rejected without discussion.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR",
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30925",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party and point of sale.",
      "universal": false,
      "remediation": "Render the checkbox unchecked on first paint. Check your form builder's default — Shopify, Klaviyo and most form plugins ship marketing consent pre-enabled, so this is usually a settings toggle rather than a code change.",
      "pitfalls": [
        "Capturing the screenshot after you clicked the box proves nothing — take it on a fresh page load."
      ],
      "catalogIds": [
        "CON-052"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-ADJACENT",
      "slug": "con-disc-adjacent",
      "title": "Disclosures must appear at the point of opt-in, not only in linked documents",
      "statement": "The required disclosures must be visible on the consent surface itself; burying them in a linked Terms page does not satisfy the requirement.",
      "rationale": "AWS states the point directly: disclosures must be presented at the time of opt-in rather than housed inside a linked policy document. A consumer deciding whether to tick a box does not open a terms page first, so a disclosure they must navigate to is a disclosure they never saw.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move the disclosure text into the same visual block as the checkbox. Keep the links to Terms and Privacy as well — they are required in addition to the inline disclosure, never as a substitute for it.",
      "example": "By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "catalogIds": [
        "CON-107"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-AUTOMATED",
      "slug": "con-disc-automated",
      "title": "Consent disclosure must disclose automated, recurring messaging",
      "statement": "The consumer must be told the messages are automated and recurring — a TCPA express-written-consent element.",
      "rationale": "TCPA consent is specifically consent to be contacted by an automatic telephone dialing system. A disclosure that reads like a human will occasionally text does not obtain that consent, so the opt-ins collected under it are legally weaker than they appear.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use \"recurring automated marketing text messages\" in the consent sentence. Both words matter: \"automated\" carries the TCPA element and \"recurring\" sets the frequency expectation.",
      "example": "By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "catalogIds": [
        "CON-097",
        "CON-098"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-BRAND",
      "slug": "con-disc-brand",
      "title": "Consent disclosure must identify the sending brand by name",
      "statement": "The disclosure at the point of consent must name the business that will send the messages.",
      "rationale": "Consent runs to a specific sender, not to messaging in general — that is what makes it non-transferable. A consumer who agreed to hear from \"us\" on a page they no longer remember cannot meaningfully have consented to the brand that later appears in their inbox, which is precisely the complaint pattern carriers built 10DLC to suppress.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the registered brand name inside the consent sentence itself, not just in the page header or logo. Use the same name you registered — if you registered \"Acme Coffee Co, LLC\" with DBA \"Acme Coffee\", either is fine, but a third marketing alias is not.",
      "example": "By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "catalogIds": [
        "CON-081"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-FREQ",
      "slug": "con-disc-freq",
      "title": "Consent disclosure must state message frequency",
      "statement": "The disclosure must tell the consumer how often messages will arrive.",
      "rationale": "Frequency is the expectation that determines whether your third message of the week reads as a service or as spam. Undisclosed frequency is a leading driver of opt-outs and complaints, and a high opt-out rate is itself a signal carriers use to throttle a campaign.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a frequency statement to the disclosure. \"Message frequency varies\" is accepted everywhere and is the safe default if your volume is irregular; a concrete cap like \"up to 4 messages per month\" is stronger if you can honour it.",
      "example": "Message frequency varies. Message and data rates may apply.",
      "pitfalls": [
        "Promising a numeric cap you exceed is worse than saying \"varies\" — the cap becomes a claim you are held to."
      ],
      "catalogIds": [
        "CON-084"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-HELP",
      "slug": "con-disc-help",
      "title": "Consent disclosure must explain how to get help",
      "statement": "The disclosure must tell the consumer they can reply HELP for assistance.",
      "rationale": "HELP is the industry-standard way a confused recipient finds out who is texting them and how to reach a human. Without it, a consumer who does not recognise your sender ID has no route other than reporting the message as spam.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply HELP for help.\" to the consent disclosure, and make sure your HELP auto-reply actually names your brand and gives a real support contact.",
      "example": "Reply STOP to unsubscribe, HELP for help.",
      "catalogIds": [
        "CON-087"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-NO-POPUP",
      "slug": "con-disc-no-popup",
      "title": "Terms must not be presented only in a pop-up",
      "statement": "A modal or pop-up is not an acceptable method of displaying the required terms.",
      "rationale": "CTIA excludes pop-ups explicitly because they are dismissed reflexively, blocked by browsers, and absent from any screenshot a reviewer takes. A term the consumer can close without reading has not been presented.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social and email.",
      "universal": false,
      "remediation": "Render the consent terms inline on the page beneath the checkbox rather than behind a modal, tooltip, or expandable \"read more\" that starts collapsed.",
      "catalogIds": [
        "CON-116"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-NOT-CONDITION",
      "slug": "con-disc-not-condition",
      "title": "Promotional consent must state it is not a condition of purchase",
      "statement": "A promotional opt-in must carry the explicit statement that consent is not a condition of any purchase.",
      "rationale": "This is a required element of TCPA express written consent, not a courtesy. Consent extracted as the price of completing a purchase is not freely given, and a plaintiff can void every opt-in collected that way — which is why carriers treat the missing sentence as a blocking defect rather than a wording preference.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Add the sentence \"Consent is not a condition of any purchase.\" to the disclosure, and make sure it is true — the form must submit with the box unchecked.",
      "example": "By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "pitfalls": [
        "Adding the sentence while the form still refuses to submit without the box ticked fails the stricter CON-NOT-GATED rule instead."
      ],
      "catalogIds": [
        "CON-073"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-PRIVACY-LINK",
      "slug": "con-disc-privacy-link",
      "title": "A Privacy Policy link must sit at the point of consent",
      "statement": "A link to the Privacy Policy must be present beside the opt-in control, not only in the page footer.",
      "rationale": "The privacy policy is where the consumer learns their number will not be sold on — the single clause carriers care most about. CTIA requires all applicable privacy policies to be referenced in and accessible from the initial call to action, so distance from the opt-in is itself the violation.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social and email.",
      "universal": false,
      "remediation": "Add a Privacy Policy hyperlink inside or immediately beneath the consent sentence. Make sure the page it points at actually contains the SMS data clause, or you will pass this rule and fail the policy-page one.",
      "example": "By checking this box you agree to Acme Coffee's <a href=\"/terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.",
      "catalogIds": [
        "CON-092"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-PURPOSE",
      "slug": "con-disc-purpose",
      "title": "Consent disclosure must state the message type or purpose",
      "statement": "The consumer must be told what kind of messages they are agreeing to receive.",
      "rationale": "Consent is scoped to what was disclosed. A consumer who opted in for order updates and then receives promotions did not consent to those promotions, and their complaint is well-founded — this is the single most common source of consumer-initiated spam reports, which are what drive carrier filtering against your numbers.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the message category explicitly and make it match your declared use case — \"marketing and promotional messages\", \"appointment reminders\", \"order and delivery updates\". If you send more than one category, say so, and consider whether they need separate opt-ins.",
      "example": "By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help. See our Terms of Service and Privacy Policy.",
      "pitfalls": [
        "Declaring MARKETING as the use case while the disclosure only mentions \"account updates\" fails both this rule and the use-case consistency check."
      ],
      "catalogIds": [
        "CON-083"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-RATES",
      "slug": "con-disc-rates",
      "title": "Consent disclosure must carry the message and data rates notice",
      "statement": "\"Message and data rates may apply\" (or a clear equivalent) must appear at the point of consent.",
      "rationale": "The consumer may be paying their carrier per message, and the disclosure exists so nobody is billed for something they did not knowingly accept. It is one of the oldest and most mechanically-checked elements in the industry, which makes its absence a cheap and very common rejection.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Message and data rates may apply.\" to the consent disclosure, adjacent to the checkbox. A rates notice that lives only in your Terms page does not count.",
      "example": "Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe, HELP for help.",
      "catalogIds": [
        "CON-085"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-STOP",
      "slug": "con-disc-stop",
      "title": "Consent disclosure must explain how to opt out",
      "statement": "The disclosure must tell the consumer they can reply STOP to stop receiving messages.",
      "rationale": "A consumer who does not know how to leave complains to their carrier instead, and carrier complaints are what trigger number blocking and violation fees. Telling them the exit at the moment they enter is both a legal requirement and the cheapest protection you have against a spam report.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent disclosure text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "FCC",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to unsubscribe.\" to the consent disclosure. Name the keyword — a general promise that they can unsubscribe any time does not tell them how.",
      "example": "Reply STOP to unsubscribe, HELP for help.",
      "catalogIds": [
        "CON-086"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DISC-TERMS-LINK",
      "slug": "con-disc-terms-link",
      "title": "A Terms of Service link must sit at the point of consent",
      "statement": "A link to the Terms of Service must be present beside the opt-in control, not only in the page footer.",
      "rationale": "The terms are where the full program description lives, and CTIA requires them to be reachable from the initial call to action rather than hunted for. A footer link at the bottom of a long checkout page is not, in practice, available at the moment of decision.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social and email.",
      "universal": false,
      "remediation": "Add a Terms of Service hyperlink inside or immediately beneath the consent sentence, next to the checkbox. Keep the footer link as well — this is in addition to it, not instead of it.",
      "example": "By checking this box you agree to Acme Coffee's <a href=\"/terms\">Terms of Service</a> and <a href=\"/privacy\">Privacy Policy</a>.",
      "catalogIds": [
        "CON-091"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-DOUBLE-OPTIN-CART",
      "slug": "con-double-optin-cart",
      "title": "Abandoned-cart programs require double opt-in",
      "statement": "Abandoned-cart messaging requires a confirmed double opt-in and must not treat cart placement as consent.",
      "rationale": "Putting an item in a cart is explicitly not consent to be texted about it, and the number is usually linked to the cart by a cookie the consumer never saw. T-Mobile therefore mandates a confirmed double opt-in for this pattern specifically — one of the very few places double opt-in is required rather than optional.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a confirmation step so the consumer opts in twice, disclose in your terms how the phone number is linked to the cart, send at most one notification per shopping event within 48 hours, and never allow order completion by text reply.",
      "notes": "Double opt-in is OPTIONAL under CTIA generally. Do not generalise this rule into a universal double-opt-in requirement.",
      "catalogIds": [
        "MSG-070"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-FLOW-METHOD-MATCH",
      "slug": "con-flow-method-match",
      "title": "The described opt-in method must match the evidence supplied",
      "statement": "The collection method described in the message flow must be consistent with the artifact and the website.",
      "rationale": "A mismatch between the description and the evidence reads to a reviewer as either carelessness or misdirection, and both get rejected. It also usually means one of your real collection surfaces is undocumented and therefore unaudited.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent artifact",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the flow describe the same method the evidence shows. If you genuinely collect consent several ways, describe each one and attach evidence for each — partial evidence is what triggers the mismatch.",
      "example": "Consent is collected two ways: (1) the web form at acmecoffee.com/signup [screenshot attached], and (2) verbally at our counter using the attached script.",
      "catalogIds": [
        "CON-007"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-FLOW-SPECIFIC",
      "slug": "con-flow-specific",
      "title": "The message flow must describe a real, specific opt-in",
      "statement": "The message flow must name where consent happens concretely enough for a reviewer to find and verify it.",
      "rationale": "Reviewers verify by going and looking. A flow that says \"customers opt in on our website\" gives them nowhere to go, so they reject rather than hunt — this is the single most common campaign-level rejection reason across every provider.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30896",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the exact surface — the page URL, the form, the physical location, or the script — and quote the consent wording the consumer agrees to. Add the link to your hosted evidence.",
      "example": "Customers opt in at https://acmecoffee.com/signup by ticking an unchecked box reading: \"By checking this box, you agree to receive recurring automated marketing text messages from Acme Coffee at the number provided. Consent is not a condition of any purchase. Message frequency varies. Message and data rates may apply. Reply STOP to unsubscribe or HELP for help.\" Screenshot: https://cdn.acmecoffee.com/compliance/optin.png",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-HELP-BACKED",
      "slug": "con-help-backed",
      "title": "Declared HELP support must have a help message behind it",
      "statement": "A campaign declaring HELP keywords must supply the help reply that will be sent.",
      "rationale": "Declaring HELP support without configuring a reply produces the worst possible outcome: a consumer asks who is texting them and gets silence or an error. CTIA requires HELP to always produce a response, whether or not the requester is even subscribed.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.help_keywords + campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "CTIA",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the HELP auto-reply, naming the brand and giving a real support contact a person can actually reach.",
      "example": "Acme Coffee: for help, email support@acmecoffee.com or call 415-555-0134. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "catalogIds": [
        "CMP-185",
        "CMP-191"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-IMPORTED-LIST",
      "slug": "con-imported-list",
      "title": "Imported or legacy lists must evidence original consent",
      "statement": "A campaign messaging a pre-existing list must describe when and how that consent was originally obtained.",
      "rationale": "An existing customer relationship is not by itself consent to send marketing texts. Where the original opt-in cannot be described, there is nothing to audit, and the practical risk is that the list predates any SMS disclosure at all.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "CTIA",
        "TCR",
        "FCC"
      ],
      "applicability": {
        "consentMethods": [
          "imported"
        ]
      },
      "applicabilityText": "Applies when consent was collected by imported list.",
      "universal": false,
      "remediation": "Describe the original collection surface and approximate date, and attach evidence of the disclosure used then. Where you cannot, re-permission the list with a fresh opt-in before messaging it.",
      "example": "Numbers were collected between 2023 and 2026 at checkout on acmecoffee.com using the disclosure shown in the attached screenshot; the consent date is stored per record.",
      "catalogIds": [
        "CON-140"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-IVR-CONFIRM",
      "slug": "con-ivr-confirm",
      "title": "IVR opt-in must capture an unambiguous confirmation step",
      "statement": "An IVR flow must require a distinct keypress or spoken confirmation specifically for messaging consent.",
      "rationale": "In an automated menu it is trivially easy for a caller to reach the end of a flow without having agreed to anything in particular. A dedicated confirmation step is what makes the resulting log entry mean \"yes to texts\" rather than \"completed the call\".",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "ivr_script",
      "authorities": [
        "TCR",
        "FCC"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by IVR.",
      "universal": false,
      "remediation": "Add a dedicated consent step with its own keypress, after the disclosure has been read, and log the keypress with a timestamp.",
      "example": "\"To agree to receive recurring automated text messages from Acme Coffee at this number — message frequency varies, message and data rates may apply, reply STOP to cancel — press 1 now. To continue without text messages, press 2.\"",
      "catalogIds": [
        "CON-157"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-KEYWORD-CONFIRM",
      "slug": "con-keyword-confirm",
      "title": "Keyword opt-in requires a compliant confirmation reply",
      "statement": "The auto-reply sent after a keyword opt-in must carry the brand, frequency, rates, STOP and HELP.",
      "rationale": "The confirmation message is the consumer’s receipt: it is where they learn what they just joined and how to leave. CTIA treats it as a required disclosure surface in its own right, and it is the one element of a keyword program you control completely.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Write the opt-in confirmation to include all five elements within the 320-character limit.",
      "example": "Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "catalogIds": [
        "CMP-181"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-KEYWORD-CTA",
      "slug": "con-keyword-cta",
      "title": "Keyword opt-in requires the advertised call to action",
      "statement": "A text-to-join campaign must supply the advertisement wherever the keyword is promoted, carrying the full disclosure.",
      "rationale": "With text-to-join, the advertisement is the disclosure — by the time the consumer texts your keyword the decision is already made. Whatever the poster, page or ad said at that moment is the entire basis of their consent, so that is the artifact that has to be compliant.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "keyword_cta_copy",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "consentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Supply a capture of the advertised CTA — the web page, sign, or print ad — showing the keyword, the number, and the disclosure elements together.",
      "example": "Text JOIN to 55512 to get offers from Acme Coffee. Msg frequency varies. Msg & data rates may apply. Reply STOP to cancel, HELP for help. Terms: acmecoffee.com/terms · Privacy: acmecoffee.com/privacy",
      "catalogIds": [
        "CMP-098",
        "CMP-099"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-KW-REVOCATION-SET",
      "slug": "con-kw-revocation-set",
      "title": "Declared opt-out keywords should cover the FCC per-se set",
      "statement": "The declared opt-out keywords should include the FCC per-se revocation words: STOP, QUIT, END, REVOKE, OPT OUT, CANCEL, UNSUBSCRIBE.",
      "rationale": "Since the FCC revocation rule took effect, those seven words are per se reasonable ways to revoke consent — a consumer using any of them has legally opted out whether or not your platform recognises the keyword. Declaring the full set is how you make your configuration match the legal reality instead of relying on STOP alone.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.optout_keywords",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare all seven per-se keywords, and confirm your sending platform honours each of them. Keywords must be alphanumeric, so use OPTOUT rather than \"OPT OUT\".",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "notes": "Registration-side twin of the runtime obligation — we verify the declared set, not runtime handling. Absorbs OPS-028, the platform-extended keyword set: OPTOUT is one of the seven required here, and the two remaining extras, HALT and STOPALL, are surfaced on a pass rather than required, because they are one provider's published list rather than the industry baseline and failing every other sender for them would be wrong.",
      "catalogIds": [
        "CMP-186",
        "OPS-028"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-NO-TRANSFER",
      "slug": "con-no-transfer",
      "title": "Consent must not be purchased, shared, or transferred",
      "statement": "Only the business that collected the consent may message the consumer; purchased lists and shared opt-ins are prohibited.",
      "rationale": "Consent is non-transferable and non-assignable — it names a sender, and it does not survive being sold, shared with affiliates, or inherited by a sister brand. This is the rule behind the affiliate-marketing prohibition and behind most of the enforcement actions that produced 10DLC in the first place, which is why it is a hard stop rather than a fixable finding.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "A purchased, rented, affiliate-sourced or partner-shared list cannot be registered under any use case. The only remedy is to collect fresh consent directly, under this brand, before messaging those numbers.",
      "catalogIds": [
        "CON-126",
        "CON-127",
        "CON-128"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-NOT-GATED",
      "slug": "con-not-gated",
      "title": "Declining messages must not block the primary action",
      "statement": "The consumer must be able to decline SMS and still complete the purchase, signup, or account creation.",
      "rationale": "Consent that is the price of checkout is coerced, and coerced consent is not consent — the FCC treats it as void and carriers treat it as a blocking defect. It is also self-defeating commercially: a list built this way has the opt-out and complaint rates of a purchased list.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "FCC",
        "TCR",
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30923",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30931",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "excludeUseCases": [
          "TWO_FACTOR_AUTH",
          "SECURITY_ALERT",
          "FRAUD_ALERT"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT TWO_FACTOR_AUTH, SECURITY_ALERT and FRAUD_ALERT.",
      "universal": false,
      "remediation": "Remove any required-field validation on the consent control and test the form end to end with the box unchecked. Remove asterisks or \"required\" markers from the messaging opt-in, and offer email or voice as an alternative channel where a contact method is genuinely needed.",
      "pitfalls": [
        "A 2FA or fraud-alert program legitimately requires the number — those use cases are excluded from this rule rather than exempted by argument."
      ],
      "catalogIds": [
        "CON-066",
        "CON-067"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-OPTOUT-CONFIRM",
      "slug": "con-optout-confirm",
      "title": "Opt-out confirmation must acknowledge and must not promote",
      "statement": "The opt-out confirmation must confirm no further messages will be sent, name the brand, and contain no promotional content.",
      "rationale": "The confirmation is the last message you are permitted to send, and using it to pitch a discount is treated as continuing to market to someone who just revoked consent. It is a Severity-0 style finding precisely because it happens after the consumer said stop.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reduce the confirmation to an acknowledgement: brand, confirmation that messages have stopped, and optionally how to get help. Remove any offer, discount, survey, or \"are you sure\" retention prompt.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "pitfalls": [
        "\"Sorry to see you go — here's 10% off if you stay\" is a marketing message sent after revocation."
      ],
      "catalogIds": [
        "CMP-189",
        "MSG-084"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-PAPER-ELEMENTS",
      "slug": "con-paper-elements",
      "title": "Paper consent form must carry the disclosure, a tick box, and a signature line",
      "statement": "The form must show the full consent disclosure, a messaging tick box separable from the phone field, and a signature and date.",
      "rationale": "A phone-number line on an intake form is contact information, not consent to be marketed to. The separable tick box is what turns a form someone filled in for another purpose into an auditable messaging opt-in, and the signature and date are what let you evidence when it happened.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "paper consent form",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "paper_consent_form",
      "authorities": [
        "TCR",
        "CTIA",
        "FCC"
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "employer_hr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form and employer / HR.",
      "universal": false,
      "remediation": "Redesign the form so SMS consent is its own tick box with the full disclosure printed beside it, positioned so a customer can give a phone number without ticking it, plus a signature and date line.",
      "notes": "whenUnreadable is FAIL, against the default. A paper form is evidence the brand chose to submit; if we cannot read the tick box or the disclosure, neither can a reviewer holding the same scan, so an unreadable form fails the same way an absent one does. The user must re-scan it themselves — flatbed or a document-scanner app, not a photograph of a page at an angle.",
      "catalogIds": [
        "CON-153"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-PAPER-FORM",
      "slug": "con-paper-form",
      "title": "Paper opt-in requires a copy of the form",
      "statement": "A campaign collecting consent on paper must supply the form, showing the disclosure and the consent control.",
      "rationale": "Like a verbal script, a paper form is invisible to a crawler — the only way anyone verifies it is if you produce it. Intake forms, waivers and applications are also where messaging consent most often gets bundled into a signature that meant something else entirely.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "paper_consent_form",
      "authorities": [
        "TCR",
        "Aerialink",
        "CTIA"
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "pos",
          "employer_hr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, point of sale and employer / HR.",
      "universal": false,
      "remediation": "Attach a scan or PDF of the blank form (not a completed one with customer data on it). We can generate a compliant form with the disclosure, a dedicated tick box, and a signature and date line.",
      "pitfalls": [
        "Do not upload a completed form containing a real customer’s number — supply the blank template."
      ],
      "catalogIds": [
        "CMP-107"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-QR-DEST",
      "slug": "con-qr-dest",
      "title": "QR opt-in must resolve to a compliant surface",
      "statement": "A QR code used for opt-in must land on a page carrying the full consent disclosure, or the disclosure must be printed beside the code.",
      "rationale": "A QR code carries no information a consumer can evaluate — it is an opaque instruction to trust wherever it goes. The disclosure therefore has to exist either next to the code or on the page it opens, and a QR that fires a pre-filled SMS bypasses both.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "qr_landing_copy",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Point the QR at a hosted opt-in page carrying the full disclosure and a real checkbox, and supply that URL or a capture of it. If the code appears on print where you also control the surrounding space, print the disclosure beside it as well.",
      "pitfalls": [
        "A QR that opens a pre-filled \"text JOIN to 55512\" message collects no disclosed consent at all — the consumer never saw terms."
      ],
      "catalogIds": [
        "WEB-102"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-RECORD-FIELDS",
      "slug": "con-record-fields",
      "title": "Consent records must capture the required fields",
      "statement": "The flow should evidence retention of the consent record: number, timestamp, medium, the exact wording agreed to, and the consenting party.",
      "rationale": "CTIA enumerates the fields a consent record must hold, including a capture of the language and action used to secure it. Recording only \"opted in: true\" leaves you unable to show WHAT was agreed to, which is the question every complaint turns on.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State in the flow that you retain the consent record, and store the disclosure version alongside the timestamp so you can reproduce what the consumer actually saw.",
      "example": "For each opt-in we store the phone number, timestamp, collection medium, the disclosure version shown, and the session or agent identity.",
      "notes": "Registration-side twin of a runtime obligation — we verify the flow claims retention, not that retention happens.",
      "catalogIds": [
        "OPS-200",
        "OPS-206"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-SCOPE-SPLIT",
      "slug": "con-scope-split",
      "title": "Promotional consent must be separate from transactional consent",
      "statement": "A single opt-in must not bundle promotional marketing with informational or transactional messaging.",
      "rationale": "These are different consents with different legal standards: transactional messages can often be sent as part of delivering a service, while marketing requires express written consent that must be optional. One combined box forces the stricter standard onto the whole program and leaves you unable to prove marketing consent for anyone.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "consent surface",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30913",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Offer two independently checkable boxes — one for order and account updates, one for promotional offers. The promotional box must be optional and unchecked; the transactional one may be required if the messages are the service.",
      "example": "☐ Send me order and delivery updates by text.\n☐ Send me promotional offers and marketing by text (optional). Consent is not a condition of any purchase.",
      "catalogIds": [
        "CON-159"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-VERBAL-ELEMENTS",
      "slug": "con-verbal-elements",
      "title": "Verbal script must contain all eight required consent elements",
      "statement": "The script must capture explicit consent and state the brand, message types, frequency, rates, opt-out, help, and the privacy policy.",
      "rationale": "Each of the eight elements carries its own DCA reject code, so a script missing one is rejected for that specific reason rather than generically — and the fix is correspondingly specific. Oral consent is only valid where it captures the same substance a written disclosure would, which is why the element list mirrors the web one.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "verbal script",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "verbal_script",
      "authorities": [
        "TCR",
        "FCC",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "TCR/DCA",
          "code": "2105",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2108",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2109",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2110",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2111",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2112",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "2119",
          "remediable": true
        },
        {
          "provider": "TCR/DCA",
          "code": "7107",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Amend the script so every element is spoken, and have the agent capture an explicit \"yes\" rather than treating silence or continuing the call as agreement. Record the call or log the consent timestamp against the customer record.",
      "example": "\"Before we finish — would you like to receive text messages from Acme Coffee about your orders and our promotions? Message frequency varies, message and data rates may apply, you can reply STOP at any time to unsubscribe or HELP for help, and our privacy policy is at acmecoffee.com/privacy. Can I confirm you agree to receive these texts at the number ending 4471?\"",
      "catalogIds": [
        "CMP-109"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-VERBAL-RECORD",
      "slug": "con-verbal-record",
      "title": "Verbal consent must describe how assent is recorded",
      "statement": "The flow must state how the consumer’s spoken agreement is captured and retained — recording, transcript, IVR log, or timestamped CRM entry.",
      "rationale": "Spoken consent evaporates unless it is written down. If a complaint arrives eighteen months later, the retained record is the only thing standing between you and an unprovable claim, and both the FCC and CTIA expect the record to exist.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Add a sentence to the message flow describing capture and retention. Name the system of record and what is stored against the customer.",
      "example": "Calls are recorded, and the agent logs the consent timestamp, the script version read, and the number consented against the customer record in our CRM. Records are retained for at least 4 years.",
      "pitfalls": [
        "Two-party consent states constrain call recording — where you cannot record, log the timestamp and script version instead."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "CON-VERBAL-SCRIPT",
      "slug": "con-verbal-script",
      "title": "Verbal opt-in requires the documented script",
      "statement": "A campaign collecting consent by phone must supply the exact script read to the consumer.",
      "rationale": "A verbal opt-in leaves no artifact unless you make one — there is no page to crawl and no box to screenshot. The script IS the consent surface, so it is the only thing a reviewer can inspect to decide whether what your agents say actually obtains consent.",
      "layer": "CONSENT_FLOW",
      "layerSlug": "consent-flow",
      "object": "campaign.message_flow + consent artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "verbal_script",
      "authorities": [
        "TCR",
        "FCC",
        "Aerialink"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Attach the verbatim script your agents read, not a summary of it. We can generate a compliant script covering all eight required elements, which you can adapt and hand to your team.",
      "pitfalls": [
        "\"Agents ask for permission to text\" describes a policy, not a script — a reviewer cannot audit it."
      ],
      "catalogIds": [
        "CMP-108"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-008",
      "slug": "msg-008",
      "title": "Opt-in confirmation must be 20–255 characters",
      "statement": "The opt-in confirmation must be at least 20 and at most 255 characters.",
      "rationale": "The confirmation has the longest required element list of the three auto-replies — brand, programme, frequency, rates, HELP and STOP — so it is the one most likely to overrun. Truncation drops the tail, and the tail is where STOP lives.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Compress to 255 characters while keeping all six required elements. Abbreviate \"Msg & data rates may apply\" rather than dropping it.",
      "example": "Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "catalogIds": [
        "CMP-007"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-009",
      "slug": "msg-009",
      "title": "HELP reply must be 20–255 characters",
      "statement": "The HELP response must be at least 20 and at most 255 characters.",
      "rationale": "Below 20 characters the reply cannot carry the brand name and a contact route, which are the two things it exists to deliver. Above 255 it exceeds the portable ceiling and is truncated at some providers — losing the tail, which is usually where the opt-out instruction sits.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fit the HELP reply into 255 characters carrying the brand name, a real support contact, the rates notice and the opt-out instruction. Drop marketing language before dropping any of those.",
      "example": "Acme Coffee: for help email support@acmecoffee.com or call 415-555-0134. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-011",
      "slug": "msg-011",
      "title": "Opt-out confirmation must be 20–255 characters",
      "statement": "The STOP confirmation must be at least 20 and at most 255 characters.",
      "rationale": "This is the last message you are permitted to send, so it must fit in one segment and say everything it needs to in one pass. A truncated opt-out confirmation can lose the acknowledgement itself, leaving the consumer unsure whether the unsubscribe worked.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bird",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Fit the confirmation into 255 characters: brand name, the acknowledgement, and that no further messages will be sent.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-015",
      "slug": "msg-015",
      "title": "A message must not render as ten or more segments",
      "statement": "No sample message may be long enough to render as 10 or more SMS segments.",
      "rationale": "Every segment is billed and delivered separately, and long concatenated messages are reassembled unreliably across carriers — so a ten-segment message costs ten times as much and is the most likely thing in your programme to arrive broken. Reviewers read extreme length as a sender who does not understand the channel.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Shorten the message and move detail behind a link on your own domain. Aim for one or two segments — 160 GSM characters per segment, or 70 if the message contains any non-GSM character.",
      "example": "Acme Coffee: your order is ready. Details: https://acmecoffee.com/o/4471 Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-018",
      "slug": "msg-018",
      "title": "An MMS campaign must attach at least one sample media file",
      "statement": "A campaign that requests MMS capability must supply at least one sample media file alongside the sample message text.",
      "rationale": "MMS is approved on what the recipient will actually see, and for a picture message that is the picture — a reviewer who is shown only the caption has no way to tell a product photo from something that would fail SHAFT screening on sight. Businesses miss this because the sample text field accepts their submission happily and nothing on the form says the attachment is a separate requirement, so the rejection arrives for a field they never noticed was empty.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "MISSING_CONDITIONAL_FIELD",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Attach the actual image or video you will send to the campaign's MMS sample media, one file per sample message that carries media. Done when every MMS sample has a file next to its text, not just a caption describing one.",
      "example": "mms_sample_media: acme-summer-sale.png (image/png, 214 KB) — the same artwork the weekend promotion will send.",
      "pitfalls": [
        "Turning MMS on and leaving the attachments for later is the usual cause: the capability flag and the sample media are separate fields, and the flag is what puts you under this requirement."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-019",
      "slug": "msg-019",
      "title": "Each MMS sample file must sit under its per-type size ceiling",
      "statement": "Every attached MMS sample file must be at most 500 KB for GIF, JPEG and PNG images, and at most 600 KB for any other media type.",
      "rationale": "The ceiling is the handset's, not the reviewer's: an oversized MMS is transcoded or dropped somewhere in the carrier chain, so the recipient gets a broken message and the sender never learns why. Registration rejects it up front instead. This catches out businesses who attach the print-resolution asset their designer handed over, because nothing about a 3 MB PNG looks wrong until it is measured.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "INVALID_FIELD_VALUE",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Re-export each attachment under its ceiling — 500 KB for GIF, JPEG and PNG, 600 KB for everything else — and re-upload. Resizing to 1200 px on the long edge and exporting at 80% JPEG quality clears it for almost any promotional image. Done when every file in the sample media list reports a size below its own limit.",
      "example": "acme-summer-sale.png re-exported at 1200x628 → 214 KB (limit 500 KB for PNG).",
      "pitfalls": [
        "The tighter 500 KB bound is keyed on the file type, not on it being an image: a WebP or a short MP4 gets the 600 KB allowance, a PNG does not."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-020",
      "slug": "msg-020",
      "title": "MMS sample images must not be placeholder or stock artwork",
      "statement": "The attached MMS sample media must show the artwork the campaign will actually send, not a placeholder graphic or an unlicensed stock image standing in for it.",
      "rationale": "The picture is how a reviewer decides whether an MMS programme is what it claims to be, so a grey \"image goes here\" block or a generic stock photo leaves them judging the campaign on the caption alone — and campaigns that register with filler artwork are the ones that later send something else. The honest cause is usually sequencing: the artwork is still with the designer when the registration goes in, and a stand-in feels harmless.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.mms_sample_media",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "NON_COMPLIANT_MESSAGE_SAMPLES",
          "remediable": true
        }
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Replace each attachment with the finished creative for that message, carrying your own branding and your own products. Done when someone who has never seen your business could name it from the image alone.",
      "example": "acme-summer-sale.png: the Acme Coffee wordmark over a photo of the espresso blend on offer, with \"20% off this weekend\" set in the brand typeface.",
      "pitfalls": [
        "A licensed stock photo is still a fail here when nothing in the frame identifies the sender — the objection is anonymity, not the licence."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-026",
      "slug": "msg-026",
      "title": "Samples must not contain mustache or curly-brace template tokens",
      "statement": "Curly-brace and mustache tokens such as {order_id} or {{name}} must not appear in sample messages.",
      "rationale": "Square brackets are the convention reviewers read as \"this varies at send time\"; curly braces read as an unrendered template that leaked into the registration. The distinction is arbitrary but it is the one applied, and the fix costs nothing.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Convert every curly-brace or mustache token to square-bracket notation with a readable name.",
      "example": "Acme Coffee: hi [first name], order [order number] ships today. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-031",
      "slug": "msg-031",
      "title": "Sample messages must be in English",
      "statement": "Sample messages must be written in English, or an English translation supplied alongside.",
      "rationale": "US reviewers assess against US requirements in English, and a sample they cannot read cannot be checked for the disclosures and content rules everything else depends on. Sending in other languages is fine — the registration artifact needs to be readable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Supply the samples in English. Where the programme genuinely sends in another language, include both the original and an English translation in the sample.",
      "example": "Acme Coffee: su pedido está listo. / Acme Coffee: your order is ready. Reply STOP to opt out.",
      "catalogIds": [
        "CMP-025"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-034",
      "slug": "msg-034",
      "title": "Samples must not be framed as test, demo or machine-to-machine traffic",
      "statement": "Sample messages and the campaign description must describe real messages sent to real subscribers, not test traffic, demonstrations, or machine-to-machine signalling.",
      "rationale": "10DLC is a production registration: the throughput and the trust that come with it are granted on the basis that a person receives each message and consented to it, so a campaign that says it is for testing is asking for a production route to carry something that is not production traffic. Developers land here honestly, registering during integration with exactly the samples their test harness sends — and reviewers cannot tell that from a sender who intends to keep the campaign in test framing indefinitely, so they refuse it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "CAMPAIGN_NON_COMPLIANT_CONTENT_NON_SUBSCRIBER",
          "remediable": true
        },
        {
          "provider": "AWS",
          "code": "NON_COMPLIANT_USE_CASE",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the samples with the messages the campaign will send once it is live, and rewrite the description so it names the real audience and what they will receive. Done when nothing in the submission would still be true if the word \"test\" were the point — a reviewer should not be able to tell you registered during integration.",
      "example": "Acme Coffee: your order #4471 is ready for pickup at our Mission St store until 6pm today. Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "Registering a throwaway campaign to test the integration and a real one later does not work either — the test campaign is refused on this rule, so the integration has nothing to test against."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-038",
      "slug": "msg-038",
      "title": "A registered domain can substitute for the brand name in a sample",
      "statement": "Where a sample does not carry the brand name, the brand's registered domain appearing in the message can satisfy sender identification.",
      "rationale": "Sender identification exists so the recipient knows who is texting them, and a link to acmecoffee.com does that as well as the words \"Acme Coffee\". Recording this explicitly prevents the brand-name check from producing false failures on messages that are perfectly identifiable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Prefer the brand name at the start of the message. Where space is genuinely tight, a link to the registered domain identifies the sender acceptably.",
      "example": "Order ready for pickup: https://acmecoffee.com/o/4471 Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-042",
      "slug": "msg-042",
      "title": "Every message must identify who is sending it",
      "statement": "The message body must identify the entity on whose behalf it is sent, and where relevant give a contact route.",
      "rationale": "Sender identification is the foundational content requirement: a consumer receiving a text from an unknown number has no way to distinguish a legitimate business from a scam. Everything else in the framework assumes the recipient can tell who is texting them.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "T-Mobile",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Lead every message with the brand name. Where the message is sent on behalf of another entity, name that entity rather than the platform.",
      "example": "Acme Coffee: your order #4471 is ready for pickup. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-045",
      "slug": "msg-045",
      "title": "Samples must not impersonate another organisation",
      "statement": "Sample messages must not impersonate or misrepresent another business, bank, government agency, or technology company.",
      "rationale": "Impersonation is a Severity-0 finding that stops all messaging on the programme, and it attracts per-message violation fees rather than a rejection you can fix. It is also the category carriers escalate fastest, because it maps directly onto fraud.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR",
        "FTC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any content implying you are, or are authorised by, another organisation. Send only under your own registered brand identity.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-046",
      "slug": "msg-046",
      "title": "Samples must be consistent with the brand website",
      "statement": "Sample messages must describe a business consistent with what the brand website shows.",
      "rationale": "Reviewers read the samples with the website open, and a mismatch is the signature of a registration assembled from someone else's business. It is also how an undeclared use case gets discovered.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + brand website",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the samples to describe the products, services and locations the website actually shows.",
      "example": "Website sells coffee subscriptions → samples reference orders, roasts and store events.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-049",
      "slug": "msg-049",
      "title": "Opt-out instructions must name a recognised keyword",
      "statement": "Opt-out instructions in message bodies must use a widely accepted keyword, not an invented alternative.",
      "rationale": "Handsets and carrier platforms recognise a fixed keyword set, so an instruction like \"text 2 to opt out\" produces a reply nothing acts on — the consumer believes they have unsubscribed and keeps receiving messages. That is the exact scenario that turns into a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use STOP in message bodies. Reserve other keywords for the declared keyword list rather than advertising them as the primary route.",
      "example": "Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "\"Reply 2 to unsubscribe\" and \"text QUITNOW\" both fail — handsets do not route them."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-054",
      "slug": "msg-054",
      "title": "Universal opt-out keywords must be supported in copy and configuration",
      "statement": "STOP, END, CANCEL, UNSUBSCRIBE and QUIT must all be supported, both in the keyword configuration and honoured in practice.",
      "rationale": "These five are recognised at handset and carrier level regardless of configuration, so a consumer using any of them has effectively opted out whether your platform noticed or not. Declaring them aligns your suppression list with what has already happened.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add all five universal keywords to the configuration, ideally alongside REVOKE and OPTOUT.",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-056",
      "slug": "msg-056",
      "title": "A no-reply route must disclose that replies are not received",
      "statement": "Where the sending route cannot receive inbound texts, every message must clearly disclose that replies are not monitored.",
      "rationale": "Telling a consumer to \"reply STOP\" on a route that discards inbound messages is worse than saying nothing: they believe they have opted out and keep receiving messages. If replies genuinely cannot be received, the opt-out route must be stated some other way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a route that receives replies. If you genuinely cannot, disclose it in every message and give a working alternative opt-out route.",
      "example": "Acme Coffee: your order is ready. This number does not receive replies — manage texts at https://acmecoffee.com/sms-preferences",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-058",
      "slug": "msg-058",
      "title": "A recurring programme must have an opt-in confirmation",
      "statement": "Every recurring message programme must send a confirmation message when a consumer opts in.",
      "rationale": "The confirmation is the consumer's receipt: it tells them the opt-in worked, what they joined, and how to leave. Without one, the first message they receive is marketing from an unfamiliar number, which is the single most reliable way to generate a spam report.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Configure an opt-in confirmation carrying the brand, programme name, frequency, rates notice, HELP and STOP.",
      "example": "Acme Coffee: you're subscribed to offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-060",
      "slug": "msg-060",
      "title": "The opt-in confirmation must be the first message sent",
      "statement": "The confirmation must precede any other messaging to a newly opted-in consumer.",
      "rationale": "Leading with marketing to someone who has just given a number, before confirming what they joined, inverts the relationship the confirmation exists to establish. CTIA sequences it deliberately: acknowledge first, then send.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.message_flow + campaign.optin_message",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Sequence the confirmation ahead of any campaign content in your automation, and describe that ordering in the message flow.",
      "example": "On opt-in we immediately send the confirmation; campaign content begins with the next scheduled send.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-061",
      "slug": "msg-061",
      "title": "The opt-in confirmation must name the registered brand",
      "statement": "The opt-in confirmation message must contain the registered brand name or DBA, matching the brand record.",
      "rationale": "The confirmation is the first message the programme ever sends, and it arrives at a moment when the recipient has typed a number into a form and may not remember which one. If it does not say who it is from, the rational response is to treat it as spam and report it — which is why a nameless confirmation produces complaints out of all proportion to its volume. Almost every provider checks this field specifically, and it is missed because the confirmation is usually written last, as a technical acknowledgement rather than as a message to a person.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message + brand.display_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "AWS",
        "Sinch",
        "Infobip",
        "Bird",
        "TCR",
        "Aerialink",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "5104",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30927",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "AWS",
          "code": "OPT_IN_MESSAGE_MISMATCH",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the registered brand name at the start of the opt-in confirmation, before anything else — use the name on the brand record rather than a programme nickname a reviewer cannot match to it. Done when someone who has forgotten signing up could tell from the first three words who is texting them.",
      "example": "optin_message: \"Acme Coffee Rewards: you are subscribed to offers and rewards updates. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.\"",
      "pitfalls": [
        "A programme name is not the brand name unless the brand name is inside it — \"Rewards Club\" fails where \"Acme Coffee Rewards\" passes, because the reviewer is matching against the registration."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-062",
      "slug": "msg-062",
      "title": "Opt-in confirmation must name the programme",
      "statement": "The confirmation must name the programme or describe the product, so the consumer knows which subscription they joined.",
      "rationale": "A business can run several programmes on one number, and \"you are subscribed\" tells the consumer nothing about which. Naming it is what lets them recognise the later messages as expected rather than unsolicited.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the specific programme in the confirmation, not just the brand.",
      "example": "Acme Coffee Rewards: you're subscribed to weekly offers. Msg frequency varies. Msg & data rates may apply. Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-063",
      "slug": "msg-063",
      "title": "Opt-in confirmation must disclose message frequency",
      "statement": "The confirmation must state how often messages will arrive.",
      "rationale": "CTIA requires frequency in the confirmation specifically, and it is the disclosure most often present at the web opt-in and missing from the confirmation. It sets the expectation that determines whether the third message of the week reads as service or spam.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a frequency statement, matching whatever you promised at the opt-in.",
      "example": "Msg frequency varies.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-064",
      "slug": "msg-064",
      "title": "Opt-in confirmation must carry the rates disclosure",
      "statement": "The confirmation must state that message and data rates may apply.",
      "rationale": "The consumer may be billed per message by their carrier, and the confirmation is the first point at which they could act on that knowledge. It is a mechanically-checked element, so its absence is caught reliably.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Msg & data rates may apply.\" to the confirmation. Keep it even when tight on characters — it is mechanically checked, and dropping it is a certain finding while shortening the marketing copy is free.",
      "example": "Msg & data rates may apply.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-065",
      "slug": "msg-065",
      "title": "Opt-in confirmation must carry HELP instructions",
      "statement": "The confirmation must tell the consumer how to get help or give a support contact.",
      "rationale": "The confirmation arrives from a number the consumer has never seen before, so it is exactly the moment they may want to check who is texting them. Giving the HELP route here closes that loop before it becomes a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply HELP for help\" or a direct support contact to the confirmation.",
      "example": "Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-066",
      "slug": "msg-066",
      "title": "Opt-in confirmation must carry a working opt-out instruction",
      "statement": "The confirmation must include STOP instructions echoing a keyword the programme honours.",
      "rationale": "A consumer who realises immediately that they did not mean to subscribe should be able to leave from the first message. The keyword named must be one the platform actually honours, or the instruction is worse than useless.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optin_message + campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to cancel\" and confirm STOP is in your declared opt-out keyword set.",
      "example": "Reply HELP for help, STOP to cancel.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-073",
      "slug": "msg-073",
      "title": "HELP response must name the brand or programme",
      "statement": "The HELP reply must contain the registered brand or programme name.",
      "rationale": "HELP exists so a recipient who does not recognise a number can find out who is texting them. A reply that omits the name answers the wrong question and leaves reporting the message as spam as the only remaining option.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Start the HELP reply with the brand or programme name, using the name the consumer would recognise from the opt-in rather than the legal entity name.",
      "example": "Acme Coffee Rewards: for help email support@acmecoffee.com. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-074",
      "slug": "msg-074",
      "title": "HELP response must give a real support contact",
      "statement": "The HELP reply must contain at least one support contact — a phone number, an email address, or a support URL.",
      "rationale": "CTIA requires help routes not to be dead ends. A reply naming the brand but offering no way to reach it satisfies the letter of \"respond to HELP\" while failing its entire purpose, and it is a named Severity-2 audit finding.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a monitored support email, a phone number that is answered, or a support page URL on the brand domain.",
      "example": "Acme Coffee: for help email support@acmecoffee.com or call 415-555-0134. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-075",
      "slug": "msg-075",
      "title": "HELP reply support contact must be on the brand domain",
      "statement": "The support email given in the HELP response must be on the registered brand domain.",
      "rationale": "A HELP reply pointing at a domain unrelated to the brand looks like a redirection to a third party, which is precisely what reviewers screen for. It also breaks the CTIA requirement that help routes actually reach the sender rather than a dead end.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_message + brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a support address on the brand's own domain in the HELP reply, matching the brand website.",
      "example": "Acme Coffee: for help email support@acmecoffee.com. Reply STOP to unsubscribe.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-078",
      "slug": "msg-078",
      "title": "HELP must be a configured help keyword",
      "statement": "The literal keyword HELP must appear in the configured help keyword set.",
      "rationale": "HELP is the universal keyword handsets and consumers expect, and it must produce a response whether or not the requester is subscribed. Configuring only a custom alternative like INFO leaves the standard route silent.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.help_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add HELP to campaign.help_keywords, alongside any custom alternatives you support. Done when texting the bare word HELP produces the help reply for a subscriber and a non-subscriber alike.",
      "example": "help_keywords: HELP, INFO, SUPPORT",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-081",
      "slug": "msg-081",
      "title": "STOP confirmation must name the brand or programme",
      "statement": "The opt-out confirmation must contain the registered brand or programme name.",
      "rationale": "A consumer who unsubscribes from several programmes needs to know which one just stopped. An anonymous confirmation leaves them unsure whether the right subscription ended, which produces a second opt-out attempt or a complaint.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Start the opt-out confirmation with the brand or programme name, so a consumer unsubscribing from several programmes knows which one just ended.",
      "example": "Acme Coffee Rewards: you have been unsubscribed and will receive no further messages.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-082",
      "slug": "msg-082",
      "title": "STOP confirmation must acknowledge the request",
      "statement": "The confirmation must acknowledge that the opt-out request was received and acted on.",
      "rationale": "Without an explicit acknowledgement the consumer cannot tell whether their STOP registered, and the rational response to that uncertainty is to complain to the carrier. The acknowledgement is what closes the interaction.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State plainly that the consumer has been unsubscribed, in the past tense so it reads as a completed action rather than a request received.",
      "example": "Acme Coffee: you have been unsubscribed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-083",
      "slug": "msg-083",
      "title": "STOP confirmation must state no further messages will be sent",
      "statement": "The confirmation must state explicitly that the consumer will receive no further messages.",
      "rationale": "Acknowledging the request and promising the consequence are different statements, and CTIA requires both. \"We got your request\" without \"you will receive no more messages\" leaves open whether anything actually changes.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the forward-looking statement that no further messages will be sent, alongside the acknowledgement — CTIA requires both, and an acknowledgement alone leaves open whether anything changed.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-088",
      "slug": "msg-088",
      "title": "Public URL shorteners are prohibited in every registration field",
      "statement": "No public URL shortener may appear in the description, message flow, samples, HELP reply, or either confirmation message.",
      "rationale": "Shorteners hide the destination, so carriers cannot assess where a message leads and block them categorically. Tools typically check only the sample messages, but the prohibition covers every field — a shortener in the HELP reply or the opt-in confirmation is rejected exactly the same way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.* (every text field)",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30892",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30963",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "8201",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace every shortened link with the full URL on your own domain. Note the two Twilio generations disagree on whether this is fixable — assume the strict reading, where it is not resubmittable.",
      "example": "https://acmecoffee.com/sale rather than https://bit.ly/3xY2z",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-095",
      "slug": "msg-095",
      "title": "Any shortener used must be dedicated to this sender",
      "statement": "Where a short domain is used, it must be dedicated to the exclusive use of the message sender.",
      "rationale": "The published standard is stricter than \"avoid bit.ly\": CTIA requires the shortener to have a web address and IP addresses dedicated to the sender exclusively. A branded short domain running on shared redirect infrastructure still fails, which surprises brands who did the obvious thing and bought a vanity domain.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use a short domain you own on infrastructure only you use, or skip shortening and use full URLs on your primary domain — which sidesteps the requirement entirely.",
      "example": "https://acme.link/sale where acme.link is yours alone — or simply https://acmecoffee.com/sale",
      "pitfalls": [
        "A vanity domain on a shared shortener service does not satisfy the dedicated-infrastructure rule."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-098",
      "slug": "msg-098",
      "title": "Links must not pass through multi-hop redirects",
      "statement": "A URL in message content must not chain through multiple redirects that hide the eventual destination.",
      "rationale": "Redirect chains defeat the transparency the URL rules exist to create — the consumer, and the carrier, cannot know where the link ends up. It is also the standard technique for laundering a blocked destination through a clean-looking first hop.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link destinations",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the final destination directly. Where click tracking is required, use a first-party tracking path on your own domain that redirects once.",
      "example": "https://acmecoffee.com/sale (direct) rather than a tracking chain through three third-party hosts.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-101",
      "slug": "msg-101",
      "title": "Links must not be intended to deceive or cause harm",
      "statement": "Embedded links must not be designed to mislead the recipient or deliver harm.",
      "rationale": "Phishing and malware delivery are Severity-0 findings carrying \"cease all messaging\", and the audit scope explicitly includes the landing page and any downloaded artifact — not just the SMS body. This is judged on intent and presentation, not on the URL string.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + link destinations",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any link whose presentation misrepresents where it leads or what it does. Link text and destination must match.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-103",
      "slug": "msg-103",
      "title": "The page a sample links to is screened as part of the campaign",
      "statement": "The landing page reached from a link in a sample message must be free of prohibited content, and is screened whether or not it is on the registered brand domain.",
      "rationale": "A message is three words and a link, so the link is where the content actually lives — which is why Twilio's prohibited-content scope covers \"campaign descriptions, sample messages, website content, and linked URLs\" rather than stopping at the registered domain. The gap this closes is real and routinely exploited: a clean sample pointing at a dispensary menu, a sportsbook offer, or an affiliate page passes every check that reads text fields. It also catches honest senders, because a campaign link often lands on a partner or marketplace page that the brand does not control and has never read as a compliance artifact.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] URL -> landing page",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open every URL that appears in a sample and read the page a reviewer will land on, including anything it redirects to. Where the destination carries prohibited content, point the message at a page you control that does not — and if the prohibited content is the product, the campaign is not registrable on it. Done when every linked destination could be shown to a reviewer without explanation.",
      "pitfalls": [
        "Screening only the registered brand website misses this entirely: the finding is about where the message goes, and campaigns frequently link somewhere the brand record never mentions."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-104",
      "slug": "msg-104",
      "title": "Link destinations must identify their owner",
      "statement": "A landing page linked from a message must unambiguously identify the site owner as a person or registered business, with contact information.",
      "rationale": "CTIA requires the message URL and every site it redirects to to identify the owner and carry contact details such as a postal address. This is a second, independent requirement beyond not using a shortener, and almost no tooling checks it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link destinations",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Ensure every page linked from a message names the business and carries contact information, including a postal address in the footer.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-105",
      "slug": "msg-105",
      "title": "The advertised offer must match the landing page",
      "statement": "The offer described in a sample message must be the offer a recipient finds on the page the message links to.",
      "rationale": "Bait-and-switch is the complaint consumers actually make — they were told about a discount, clicked, and found a signup wall or a different price — and it is one of the named triggers for Twilio's deceptive-marketing code, which is not eligible for resubmission. Honest campaigns drift into it through timing rather than intent: the sample was written against last quarter's promotion and the page has moved on, or the deep link rots and lands on a generic homepage where the advertised offer is nowhere in sight.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] <-> landing page",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite each sample so it describes what is genuinely on the page it links to, or point the link at the page that carries the offer. Deep-link to the offer rather than to the site root, so the recipient sees the thing they were promised without hunting for it. Done when the headline on the landing page and the offer in the message say the same number.",
      "example": "Acme Coffee: 20% off all espresso blends this weekend. Shop https://acmecoffee.com/sale — and the page at /sale leads with \"20% off all espresso blends, this weekend only\". Reply STOP to opt out.",
      "pitfalls": [
        "A page that carries the offer somewhere below three screens of other content still reads as a switch — reviewers judge what is visible on arrival."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-106",
      "slug": "msg-106",
      "title": "Link domains must not appear in threat intelligence",
      "statement": "Domains appearing in message content must not be flagged by threat-intelligence sources.",
      "rationale": "Carriers screen destinations against reputation databases, so a link to a compromised or previously-abused domain gets the campaign filtered regardless of how clean the registration is. Shared hosting and recycled domains are the usual innocent cause.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] link domains",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check every domain you link against Google Safe Browsing and VirusTotal before registering, and move off any flagged host. A previously-abused domain will not clear simply by waiting.",
      "notes": "Needs a reputation lookup the product does not have; surfaced as a warning with the check the user should run themselves.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is every domain you link to in these messages clean on Google Safe Browsing and VirusTotal today?",
        "howToCheck": [
          "List the domains in the sample messages, including link-shortener destinations.",
          "Check each against Google Safe Browsing and VirusTotal.",
          "Move off any flagged host — a previously abused domain will not clear simply by waiting."
        ],
        "failureLooksLike": "A recycled domain or a shared host carries somebody else's history into your campaign, and the traffic is filtered regardless of how clean the registration is."
      }
    },
    {
      "id": "MSG-116",
      "slug": "msg-116",
      "title": "embeddedPhone must ignore the HELP support number",
      "statement": "The embeddedPhone attribute should be set when a sample contains a phone number other than the support number given in the HELP reply.",
      "rationale": "Publishing a support number in a HELP response is required, so counting it as an embedded phone number forces a true declaration that misrepresents the campaign. The attribute is meant to flag numbers inside marketing content, which is a redirection signal, not a support contact.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_phone + campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the embedded-phone attribute based on numbers in campaign content, excluding the support number you publish in the HELP reply.",
      "example": "Samples contain no numbers other than the HELP support line → leave \"messages contain phone numbers\" false.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-117",
      "slug": "msg-117",
      "title": "A sample must contain a phone number when embeddedPhone is declared",
      "statement": "When embeddedPhone is true, at least one sample must contain a phone number.",
      "rationale": "Mirror of the link rule: the attribute drives filtering for numbers inside message bodies, which is a redirection signal. Declaring it with no example leaves the reviewer unable to see what kind of number appears and why.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_phone + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include a representative sample containing the number, or set the attribute false. Remember a HELP-reply support number does not by itself require the attribute.",
      "example": "Acme Coffee: questions about your order? Call 415-555-0134. Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-119",
      "slug": "msg-119",
      "title": "A sample must contain a URL when embeddedLink is declared",
      "statement": "When embeddedLink is true, at least one sample message must actually contain a URL.",
      "rationale": "The attribute tells carriers to apply link-specific filtering, so declaring it without demonstrating a link means the campaign is registered for treatment it never exercises — and a reviewer reads the mismatch as a careless or copied registration.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.embedded_link + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either include a representative link in a sample, or set the embedded-link attribute false if the programme genuinely sends no links.",
      "example": "Acme Coffee: 20% off this weekend. Shop https://acmecoffee.com/sale Reply STOP to opt out.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-120",
      "slug": "msg-120",
      "title": "Declared attributes must match sample content generally",
      "statement": "Every declared campaign attribute must be consistent with what the sample messages actually contain.",
      "rationale": "Attributes are self-declared and drive filtering, so a mismatch either over-restricts your own traffic or under-declares something carriers screen for. The link and phone pairs are checked individually; this catches the rest as a set.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign attributes + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Walk each attribute against the samples and correct whichever side is wrong.",
      "example": "Samples mention financing → direct lending: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-123",
      "slug": "msg-123",
      "title": "Age-restricted content requires the age-gated attribute",
      "statement": "Where sample or description content covers alcohol, firearms, or tobacco, the age-gated attribute must be set true.",
      "rationale": "The attribute is what tells carriers an age gate is in place, and the underlying categories carry per-message violation fees rather than rejections. Declaring it is the difference between a restricted campaign and an enforcement event.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.age_gated + campaign content",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the age-gated attribute true and implement real date-of-birth verification at opt-in. Note several providers refuse these categories entirely even with a gate.",
      "example": "Content references alcohol → age gated: true, with DOB collected at opt-in.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-131",
      "slug": "msg-131",
      "title": "Non-GSM characters silently force Unicode encoding",
      "statement": "Characters outside the GSM-7 alphabet — smart quotes, ™, ®, em dashes, emoji — cut the per-segment limit from 160 to 70.",
      "rationale": "A curly apostrophe pasted from a word processor more than doubles the cost of every message and can split a one-segment message into three, and nothing in the authoring flow warns you. The character is visually identical to the straight version, so this is almost impossible to spot by reading.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "LOW",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace smart quotes with straight quotes, em dashes with hyphens, and remove ™, ® and emoji from message bodies. Retype rather than pasting from a word processor.",
      "example": "Acme Coffee: you're subscribed — replace the curly apostrophe with a straight one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-137",
      "slug": "msg-137",
      "title": "No unsolicited bulk messaging",
      "statement": "Campaign content must not promote unsolicited bulk messaging, whatever the content of it is.",
      "rationale": "Spam is a category in its own right in the T-Mobile code of conduct and the CTIA principles, independent of whether the content would otherwise be allowed — a perfectly ordinary discount offer sent to people who did not ask for it is refused on the sending pattern alone. This matters because it is the one prohibition a business cannot read off its own message copy: the campaign looks fine and the defect is in where the list came from and how the traffic is shaped. It also carries runtime consequences rather than only registration ones, since carrier filters score volume and complaint rate continuously.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30007",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "4470",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Say in the message flow where each recipient's consent came from, in a way that names the surface and the wording they saw, and drop any part of the list you cannot trace to one. If the answer is that the list was acquired, the campaign is not registrable on that list — this is not a copy problem and rewriting the samples will not move it.",
      "pitfalls": [
        "A clean opt-in on the front of the list does not cover the back of it: reviewers ask about the whole audience, and an imported segment inside an otherwise organic list is what the finding is usually about."
      ],
      "notes": "Runtime enforcement continues after registration — Bandwidth 4470 is a billable per-message spam rejection and Twilio 30007 is carrier filtering — so passing this rule at registration does not settle it for the life of the campaign.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-142",
      "slug": "msg-142",
      "title": "The description must match the sample messages",
      "statement": "The campaign description must describe the messages the samples actually show.",
      "rationale": "The description is the claim and the samples are the evidence, so reviewers read them against each other first. A description promising appointment reminders beside samples full of promotions is the clearest possible signal that one of the two is untrue.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite whichever is wrong so the description enumerates the message types the samples demonstrate, and the samples cover the types the description promises.",
      "example": "Description mentions promotions, order updates and event invites → one sample of each.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-148",
      "slug": "msg-148",
      "title": "Samples must match what the printed or in-person call to action promised",
      "statement": "Where consent is collected on paper, at a point of sale, by QR code or by keyword, the samples must match the programme the printed call to action described.",
      "rationale": "An offline call to action is the whole of the consent record — the consumer never saw a web page, so the poster, the form or the counter card is the only thing they agreed to. When the samples describe a different programme there is nothing to fall back on, which is why reviewers compare the two directly and why the mismatch is fatal rather than fixable by adding a disclosure. It happens because the print run is long-lived: the card was designed for appointment reminders two years ago and the programme now sends offers, and nobody reprints a counter card to match a campaign registration.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] <-> offline CTA artifact",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "pos",
          "qr",
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, point of sale, QR code and text-to-join keyword.",
      "universal": false,
      "remediation": "Read the artifact you uploaded as evidence and register the samples that match it. Where the programme has genuinely moved on, the artifact is the thing that has to change — reprint the card or the poster with the current programme described, collect consent against that, and upload the new version. Done when the content types in the samples all appear in the printed call to action.",
      "example": "Poster: \"Text SHELTER to 55512 for Riverside Shelter capacity updates and volunteer alerts. Msg frequency varies. Msg & data rates may apply.\" — samples show a capacity update and a volunteer alert, and nothing else.",
      "pitfalls": [
        "A QR code that has been reprinted but still points at the old landing page fails this on the destination rather than on the print, and the artifact looks correct in the upload."
      ],
      "catalogIds": [
        "CMP-157"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-149",
      "slug": "msg-149",
      "title": "Samples must match the declared opt-in method",
      "statement": "Sample content must be consistent with how consent was collected, not only with the use case.",
      "rationale": "The opt-in method sets expectations about what arrives: someone who texted a keyword for a discount code expects offers, while someone who ticked a box at checkout expects order updates. A mismatch means the consent obtained does not cover the messages sent.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + consent method",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Align the samples with what the opt-in surface promised. Where you send more than the opt-in described, widen the disclosure rather than narrowing the samples.",
      "example": "Keyword opt-in advertised as \"text JOIN for offers\" → samples are offers, not appointment reminders.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-152",
      "slug": "msg-152",
      "title": "Transactional campaigns must contain no promotional element",
      "statement": "A campaign declared transactional or informational must have no coupon, offer, upsell, or promotional content in its samples.",
      "rationale": "Transactional use cases receive better throughput and lighter filtering precisely because they are not marketing. A single coupon inside an otherwise transactional set means the campaign obtained that treatment under a false description, which is why one offending sample is enough to reject.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove promotional content from transactional samples, or move the campaign to MIXED and collect promotional-grade consent.",
      "example": "ACCOUNT_NOTIFICATION: \"Acme Coffee: your payment of $24.50 was received.\" — no offer attached.",
      "pitfalls": [
        "A discount code appended to an order confirmation converts the whole campaign into marketing."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-155",
      "slug": "msg-155",
      "title": "Sweepstakes content requires the sweepstakes use case",
      "statement": "Where samples contain sweepstakes or contest content, the sweepstakes use case must be declared.",
      "rationale": "Sweepstakes carry their own eligibility and official-rules requirements, and several providers subject them to special business review. Running one under a general marketing use case bypasses that review, so reviewers force the correction rather than accepting the label.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Sinch",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare the sweepstakes use case and publish official rules linked from the message, or remove contest content from the campaign.",
      "example": "Use case: SWEEPSTAKE, with official rules at acmecoffee.com/rules linked in the message.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-161",
      "slug": "msg-161",
      "title": "SHAFT screening covers every campaign field, not a sample of them",
      "statement": "The SHAFT screen — sex, hate, alcohol, firearms, tobacco and profanity — must be applied to every sample message and to the campaign description and message flow, and a single hit anywhere fails the whole campaign.",
      "rationale": "Reviewers screen the submission as one document, so four clean samples and a fifth that mentions the bar tab is a rejected campaign, not a mostly-passing one. The innocent version of this is very common: the samples are written carefully and the description is written last, in a hurry, naming the product the samples were careful to avoid. Because the rejection names the campaign rather than the field, operators resubmit with the samples polished again and are refused a second time for text they never re-read.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "AT&T",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "4100",
          "remediable": false
        },
        {
          "provider": "Twilio",
          "code": "30883",
          "remediable": false,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Find which letter was hit before changing anything, because the letters do not share a fix. Sex and hate have no path at any provider. Firearms has none under the strict reading. Alcohol and tobacco have a narrow one that runs through a 21+ date-of-birth age gate rather than through the message copy. The per-letter rule for the category you hit carries the actual path; this one only tells you the screen covers every field you submitted.",
      "pitfalls": [
        "Re-reading only the samples is the usual second failure — the description and the message flow are screened on the same pass and are where the product name most often survives."
      ],
      "notes": "Deliberately HARD_STOP rather than RETRY_FIELD even though some letters are fixable: the umbrella verdict does not know which letter fired, and a blind regenerate-and-resubmit loop against a SHAFT rejection burns a campaign at Twilio, where the Gen-2 codes are not eligible for resubmission.",
      "catalogIds": [
        "CMP-161"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-163",
      "slug": "msg-163",
      "title": "Family-friendly styling must not carry adult themes",
      "statement": "Content presented as suitable for a family audience must not carry adult themes, and must not depict children or characters aimed at children in a sexualised or otherwise unsuitable way.",
      "rationale": "The harm is that nothing on the surface warns anyone: the message reads as a cartoon promotion, so it is forwarded, shown to a child, and only then understood. Telnyx and Bird both name this shape specifically because keyword screening is blind to it — every word in the message is clean, and the adult content lives in what the styling is pointing at. The honest version is a mascot or cartoon identity attached to an adult product, adopted because it tested well and without anyone thinking about who else receives the text.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Telnyx",
        "Bird",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Bird",
          "code": "703",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Drop the child-facing styling, the mascot and the cartoon voice, and describe the product to adults in adult terms. If the product itself is adult, note that restyling only settles this rule — the underlying content is still judged by the adult-content prohibition, which has no path on 10DLC.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-164",
      "slug": "msg-164",
      "title": "No underage, non-consensual or illegal sexual themes",
      "statement": "Campaign content must not promote underage, non-consensual or otherwise illegal sexual themes, simulated or real.",
      "rationale": "This is the one band of content where carriers act on a single message and escalate outside the ordinary rejection process, because the exposure of carrying it is criminal rather than commercial. It sits apart from the general adult-content prohibition because simulation does not soften it: fiction, roleplay and \"barely legal\" framing are treated identically to the real thing, which is the distinction operators of legal adult businesses most often get wrong.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "There is no compliant framing and no age gate that reaches this content — remove it from the campaign and from every page a message links to. If the finding is a false positive on safeguarding or clinical copy, say in the description who the recipients are and what the service does, in the terms a clinician would use.",
      "notes": "AT&T additionally bans how-to material about pedophilia or non-consensual contact. Content promoting the sexual exploitation of minors is a separate obligation (catalog MSG-166) that routes to human handling rather than to an automated verdict, and no rule in this registry decides it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-170",
      "slug": "msg-170",
      "title": "No content promoting, glorifying or inciting violence",
      "statement": "Campaign content must not promote violence, threats or physical harm.",
      "rationale": "T-Mobile treats depictions and endorsements of violence as illicit content in their own right, independent of whether any protected group is targeted — so a campaign can clear the hate-speech screen and still be refused here. The consumer-protection reason is blunt: SMS is a channel with no way to warn or filter before the words are on the screen, and the recipient did not choose to see them. Businesses hit this through marketing register rather than intent, with copy about destroying the competition or killing it at the gym read literally by a screen that has no sense of humour.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "T-Mobile",
        "CTIA",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the violent framing and describe what the business actually does. Where the copy was figurative, the fix is to say the literal thing instead: \"our fastest sale of the year\" rather than \"prices we are murdering\". If violence is genuinely the subject — an entertainment title, a game — no source publishes a carve-out for promoting it by SMS.",
      "notes": "Twilio codes hate and violence to the same Gen-2 code, 30954, so a rejection under it does not tell you which of the two fired. The registry keeps them apart because the copy that causes each is different: MSG-SHAFT-HATE is content aimed at a group, this is content about harm.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-171",
      "slug": "msg-171",
      "title": "Messages must not harass, intimidate or threaten the recipient",
      "statement": "Message content must not be designed to harass, bully or intimidate the person receiving it, and must not threaten them.",
      "rationale": "This is hostility pointed at the individual holding the phone rather than at a group, and it is the shape complaints arrive in most often — the recipient reports the message because it frightened them, not because it offended them. Honest businesses land here through pressure copy and enforcement language: escalating reminders, \"final warning\", \"we will be forced to take action\", each of which reads as a threat on a small screen with no context around it. AT&T states it plainly — message content that deceives or threatens consumers is not permitted — and there is no volume threshold below which it is tolerated.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "CTIA",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the copy to state the position and the recipient's options neutrally, and delete anything that predicts a consequence in order to frighten rather than to inform. A message that says what is owed, by when, and how to resolve it carries the same information without the pressure — and if the programme genuinely depends on the pressure, it is not registrable.",
      "pitfalls": [
        "Softening the wording while keeping an escalating cadence does not settle this: repetition aimed at wearing the recipient down is itself the harassment, and the samples are read as a sequence."
      ],
      "catalogIds": [
        "MSG-172"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-175",
      "slug": "msg-175",
      "title": "Content must not invade privacy, defame, or otherwise cause harm",
      "statement": "Message content must not invade privacy, create a safety risk, or be unlawful, abusive, malicious, misleading, excessively violent, obscene or defamatory.",
      "rationale": "CTIA's residual harm clause is what catches the damage nobody wrote a category for — a message that names a third party's debt, address or medical appointment to somebody else, or that asserts something ruinous about a named business. The recipient of a privacy leak is not the person harmed by it, which is why no complaint-driven check finds these and why the clause is written as a sweep. Businesses reach it innocently through personalisation: a template that renders the wrong record, or a referral message that tells the recipient why their friend needs the service.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Strip any third-party detail out of the message body and address the recipient about their own relationship with you only. Where personalisation pulls in another person's data — a referrer, a dependent, a co-signer — replace it with a reference the recipient already holds, and remove any assertion about a named business you cannot document.",
      "pitfalls": [
        "A message that is accurate can still fail this: disclosing a true fact about someone who is not the recipient is the violation, not getting it wrong."
      ],
      "notes": "This is CTIA MPBP §5.3.1, which is written as a catch-all covering several harms that have dedicated rules elsewhere. It is kept whole rather than split so the limbs nobody else owns — privacy invasion, safety risk and defamation — are not lost between rules; the criteria route the overlapping limbs so a single defect is reported once.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-177",
      "slug": "msg-177",
      "title": "Content must not depict or facilitate substance abuse",
      "statement": "Message content must not depict, promote or be designed to facilitate the abuse of alcohol, drugs, tobacco or other controlled substances, including where the abuse is implied rather than stated.",
      "rationale": "This is the limb of the alcohol prohibition that an age gate does not reach, and confusing the two costs operators a build cycle: gating the site stops minors seeing the offer, and does nothing about copy that sells drinking to excess as the point. AT&T and the CTIA messaging principles both extend it to non-verbal implication, so an emoji, an image or a wink carries the meaning as well as a sentence does. Bars and event promoters hit this constantly and innocently, because \"bottomless\", \"all you can drink\" and \"we will not judge how many\" are ordinary trade language that reads, to a screen, as encouragement to drink to harm.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the quantity, the challenge and the intoxication as the selling point, and price or describe the offer instead. Do not reach for the age gate here — it settles who may see an alcohol offer, not what the offer says, so building one leaves this finding exactly where it was. Done when nothing in the copy would read as encouragement if a regulator read it aloud.",
      "pitfalls": [
        "An emoji can carry the whole violation on its own: a row of beer glasses beside \"Friday\" is the implication the rule is written about, and deleting the words does not remove it."
      ],
      "notes": "Graded BLOCKING on the strict reading. The research records the AT&T alcohol-abuse row at HIGH and the CTIA abuse-reference row at BLOCKING; the registry takes the stricter of the two and records the disagreement here rather than averaging it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-179",
      "slug": "msg-179",
      "title": "Alcohol content must not be aimed at people under the drinking age",
      "statement": "Alcohol brands, and drinks designed to resemble alcoholic ones, must not be marketed in a way that appeals to or targets people under 21.",
      "rationale": "An age gate is the whole basis on which alcohol messaging is permitted at all, so copy built to appeal to minors defeats the mechanism rather than failing to install it — which is why T-Mobile treats this as its own refusal rather than as an age-gating defect. Alcohol-resembling drinks are named explicitly because they slip the gate by construction: an alcohol-free brand extension in the same livery is sold to the same audience with none of the restrictions, and the brand association is the marketing. The innocent cause is a genuine 0% product line marketed on exactly the assets built for the alcoholic one.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30456",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the youth-facing appeal — the cartoon, the school or campus timing, the sweet-confectionery framing, the under-21 talent — and market the drink to adults. Note that the age gate is not the answer to this finding: gating the site is already required for alcohol content, and a campaign that targets minors is refused whether or not the gate exists.",
      "pitfalls": [
        "A zero-alcohol product does not escape this when it carries the parent brand's identity — the brand is what is being marketed, and it is an alcohol brand."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-182",
      "slug": "msg-182",
      "title": "No explosives, fireworks or pyrotechnics content",
      "statement": "Campaign content must not promote explosives, fireworks or pyrotechnics.",
      "rationale": "Twilio files explosives under the same code as firearms and toll-free verification extends the SHAFT \"F\" to fireworks explicitly, which surprises seasonal retailers who reason — correctly, in their own state — that they hold every licence the law asks for. The prohibition is about the channel rather than the licence: a text is forwarded, screenshotted and read by people the seller never sold to, and carriers will not carry the promotion of something that injures thousands of people a year across a medium with no controls on who sees it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "AT&T"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30957",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30469",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the product from the campaign entirely — the age gate that works for alcohol has no equivalent here, and no provider publishes a licensed-retailer exemption. Where the programme is genuinely operational rather than promotional, say so in the description: crew scheduling, safety notices and public event information carry no product offer and are a different campaign.",
      "notes": "The allowances above are the reframings that remove the product-promotion element, not documented exemptions — no source publishes a carve-out for public-display announcements. Treat them as the shape worth attempting, and expect the description to have to make the absence of a sale explicit.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-183",
      "slug": "msg-183",
      "title": "No weapons that are not legal in all fifty states",
      "statement": "Campaign content must not promote weapons that are not lawful in every US state.",
      "rationale": "This is a legality sweep rather than a category ban, and it is the rule that catches sellers who are entirely lawful where they stand: switchblades, brass knuckles, certain knives, tasers and high-capacity magazines are ordinary retail in some states and criminal to possess in others. A campaign is one national object — the numbers reach every state — so carriers apply the most restrictive state rather than the sender's. Bandwidth and Infobip both refuse on the fifty-state test specifically, which is why a firearms-clean knife retailer still gets rejected and cannot see why.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7009",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "7011",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7009",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the state-restricted lines out of the message copy and register the campaign on the products that are lawful everywhere, or on services rather than product. The test is nationwide legality, not your own licence, so the fix is to check each item you name against the strictest state rather than to add a disclaimer or an age gate.",
      "pitfalls": [
        "Adding \"where legal\" to the message does not settle this — carriers apply the fifty-state test to the campaign, not to each recipient, and the disclaimer is read as an admission."
      ],
      "notes": "The same code number resolves differently by namespace, so the remediability is recorded per provider rather than averaged: Bandwidth Proxy 7009 and 7011 are terminal, while Infobip 7009 is conditional on the product set being lawful nationwide. Kept apart from MSG-SHAFT-FIREARMS deliberately: that rule is the firearms category test, this one is a legality test that catches sellers who hold no firearms at all. A knife retailer trips this and passes that.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-190",
      "slug": "msg-190",
      "title": "No kratom or drug-paraphernalia content",
      "statement": "Campaign content must not promote kratom, drug paraphernalia and smoking accessories.",
      "rationale": "Kratom is unscheduled federally and sold openly in most states, which is exactly why operators are blindsided: carriers screen it alongside cannabis rather than by legal status, and the paraphernalia ban reaches the pipe, the grinder and the rolling paper even where nothing controlled is being sold with them. The consumer-protection reason is the same one behind the cannabis ban — a channel with no age assurance and no way to check what the product actually contains — and the FDA import alerts on kratom are what keeps it there.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Sinch",
        "Infobip",
        "Plivo",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30469",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "701",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7011",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take kratom and accessory product out of the message copy and out of any page a message links to. There is no age gate or licence that opens this — a shop that sells other goods can register on those, but the campaign has to be describable without naming the restricted lines, including in the description and the message flow.",
      "pitfalls": [
        "Screening only the samples leaves the usual hit in place: the shop's name and the linked landing page are read on the same pass, and a store called anything with \"smoke\" or \"head shop\" in it is assessed on that alone."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-206",
      "slug": "msg-206",
      "title": "No sweepstakes, raffle or contest-entry content",
      "statement": "Campaign content must not promote sweepstakes, raffles, prize giveaways or contest entry.",
      "rationale": "Twilio files sweepstakes under gambling and refuses the category outright for \"high abuse potential and regulatory complexity\", while TCR and Sinch publish a Sweepstakes use case you can register under — so the same giveaway is a legitimate programme on one route and a dead campaign on another. That split is what makes this expensive: a marketing team runs the giveaway that worked last year, registers it as MARKETING, and loses the campaign rather than being told to change one dropdown. Prize mechanics also carry real consumer harm on SMS, because a giveaway is the cheapest way ever invented to buy a phone number from someone who was not thinking about what they were joining.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AWS",
        "Bandwidth",
        "Telnyx",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30945",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30462",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check whether your route publishes a Sweepstakes use case before you do anything else, because that decides whether this is a dropdown change or the end of the campaign. Where it exists, register under it and link the official rules from the message. Where it does not, replace the chance-based mechanic with a certain one — every subscriber gets the discount — and remove the draw from the samples, the description and the opt-in copy.",
      "pitfalls": [
        "Moving the draw out of the samples but leaving \"enter to win\" in the opt-in call to action does not settle it: the incentivised-opt-in limb is judged on the message flow, and that is where the offer usually lives."
      ],
      "notes": "Genuine authority divergence rather than a strictness difference: Twilio 30945 and toll-free 30462 refuse the category, while TCR and Sinch (CR1105, CR2009, CR3009) provide a Sweepstakes use case to register it under. Kept BLOCKING on the strict reading, with the use-case route named in the remediation so a user on a permissive route is not told to abandon a legitimate programme.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-209",
      "slug": "msg-209",
      "title": "A charity programme may not run games of chance or recurring donations",
      "statement": "A campaign registered under the CHARITY use case must not solicit lottery, sweepstakes or raffle entries, and must not enrol donors in recurring donations.",
      "rationale": "The charity use case buys favourable treatment — higher throughput, a lighter consent standard than marketing — on the basis that the traffic is mission communication to supporters. Games of chance and recurring billing are the two things that turn it into a payment funnel, and both carry the harms the concession was never meant to cover: a raffle is gambling however good the cause, and a recurring donation enrolled by text is a subscription the donor cannot see, cancel or reconcile. Charities are caught out by this constantly and in complete good faith, because the raffle at the fundraising dinner and the monthly giving programme are the two most normal things a small charity does.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30462",
          "remediable": false
        }
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Take the draw and the recurring enrolment out of the text programme and keep both on the web, where the donor sees the terms and the billing schedule. The messages may link to them; they may not run them. Done when no sample asks for an entry, a ticket or a monthly commitment, and every donation ask in the campaign is for a single gift.",
      "example": "Riverside Shelter: our winter beds are at capacity tonight and we need blankets. Give once at https://riversideshelter.org/give Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "Linking to the raffle page rather than selling tickets in the thread is the fix; describing the raffle in the message and linking to it is still soliciting entries and still fails."
      ],
      "notes": "CTIA dropped this from the Short Code Monitoring Handbook at v1.9 while T-Mobile section 6.7 retains it, so it is a live requirement on the strictest reading rather than a historical one. Divergence recorded; strictest reading kept.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-210",
      "slug": "msg-210",
      "title": "A political donation programme may not include gaming-related fundraising",
      "statement": "A campaign registered under the POLITICAL use case must not raise funds through lotteries, sweepstakes, raffles or any other game of chance.",
      "rationale": "Political fundraising by text already sits under the most restrictive consent and vetting regime in the framework, and attaching a prize draw to it compounds two separate exposures: gambling regulation, which is state by state, and campaign-finance law, which requires every contribution to be attributable to a qualified individual giving their own money. A raffle ticket is not a contribution in that sense, so the mechanic breaks the record-keeping the programme is required to keep. Committees reach it because \"enter to win dinner with the candidate\" is a standard grassroots tactic on every other channel.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Remove the draw from the text programme and ask for the contribution directly. Where the prize event genuinely exists, describe it as an event with an entry price handled on the web, not as a chance the message sells. Done when every fundraising sample asks for a contribution and nothing in the campaign offers a chance to win.",
      "example": "Riverside Shelter Action Fund: we are $4,000 short of our filing deadline. Chip in at https://riversideshelter.org/contribute Reply STOP to opt out, HELP for help.",
      "notes": "CTIA states the prohibition for political donation programmes specifically. MSG-209 is the same gaming ban for charities and adds the recurring-donation limb, which is not part of this one — the two are kept apart because they are gated on different use cases and do not say the same thing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-214",
      "slug": "msg-214",
      "title": "No student-loan programme content",
      "statement": "Campaign content must not promote student-loan refinancing, consolidation or forgiveness programmes.",
      "rationale": "Student-loan messaging is refused as a category because the fraud around it is industrial: forgiveness and consolidation offers by text are the delivery mechanism for advance-fee scams that charge borrowers for a free federal process. Carriers cannot tell a legitimate refinancer from that by reading a message, so they refuse the category and let the servicers who have a genuine relationship register on it. The line that decides your campaign is whether you service the loan you are texting about.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AT&T",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the acquisition offer. If you service the loans yourself, say so in the description in those words — who originated the loan, who services it, and that every recipient is an existing borrower — and keep the samples to account events rather than offers. Done when no message would make sense to somebody who is not already your customer.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-215",
      "slug": "msg-215",
      "title": "Samples must not collect information to qualify a consumer for a loan",
      "statement": "Sample messages must not ask the recipient to supply information used to qualify, price or route a loan application.",
      "rationale": "A text asking for the last four of a social security number or the year and mileage of a car is a loan lead-generation funnel wearing the clothes of a service message, and it is refused for two reasons at once: the lending prohibition, and the fact that no legitimate lender collects underwriting data by SMS reply. The consumer harm is direct — an SMS thread is unencrypted, sits in the handset indefinitely, and is the highest-yield place a phishing operator can imitate. Businesses that genuinely lend land here by moving a working web form into a text conversation because the completion rate is better.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Nothing about the phrasing rescues this, because the objection is to collecting the data in the thread at all. Move the whole qualification step behind an authenticated page on your own domain and let the message do nothing but link to it — and if the campaign has no purpose beyond routing loan enquiries, it is not registrable whatever the messages say.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-221",
      "slug": "msg-221",
      "title": "Debt relief and credit repair stay prohibited even with first-party consent",
      "statement": "Debt consolidation, debt reduction and credit repair content is refused whether or not the sender collected the consent itself.",
      "rationale": "Consent is the answer to almost everything else in this framework, so operators reasonably assume it is the answer here too — and it is the one place it is not. Carriers refuse these categories on the business model rather than on the permission, because the consumer harm they are protecting against is the service itself: the fees, the credit damage and the misrepresentation happen to people who did agree to hear from the sender. Stating it as its own rule matters because the neighbouring first-party carve-outs are real, and a user who has just read that a creditor may chase its own invoice will otherwise conclude the same reasoning rescues a debt-settlement offer.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bird",
        "Infobip",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30949",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30950",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not answer this by strengthening the consent record — a signed opt-in, a recorded call and a double opt-in all leave the verdict where it is. The campaign is registrable only without the debt-relief and credit-repair service in it, so remove those lines from the description, the samples and the flow, and register on whatever else the business does.",
      "notes": "MSG-DEBT is the semantic screen and is what catches euphemism; this is the deterministic backstop, and it exists because the objection users actually raise is consent rather than wording. Matching on an explicit lexicon on purpose: the point is to answer the consent question before a round trip is spent on it, not to be the primary detector.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-226",
      "slug": "msg-226",
      "title": "No forex, binary options or speculative-derivative content",
      "statement": "Campaign content must not promote forex, binary options, contracts for difference and other speculative derivatives.",
      "rationale": "These instruments are the ones retail regulators warn about most loudly, and SMS is where the promotion happens because it reaches people outside any suitability check. Twilio names them separately from the general stock and crypto ban because the promotion pattern is different — signal groups, copy-trading and leverage offers rather than tips on a named security — and because binary options are outright banned to retail investors in several jurisdictions the traffic reaches.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30948",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the solicitation. Account servicing for people who already hold an account with a regulated entity is a separate, registrable category — frame the campaign that way, name the regulator and the registration in the description, and keep the samples to account events with no market commentary in them.",
      "notes": "Twilio 30948 also covers unregistered-securities offerings, which the catalog holds as a separate obligation requiring external data and human review (MSG-228). No rule in this registry decides that one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-227",
      "slug": "msg-227",
      "title": "No promises of unusually high or guaranteed returns",
      "statement": "Campaign content must not promote schemes promising guaranteed, risk-free or unusually high financial returns.",
      "rationale": "A guaranteed return is the single most reliable marker of investment fraud, which is why it is screened as its own category rather than left to the deception rules: no lawful investment can promise one, so the claim is refused on its face without anyone having to establish what the underlying product is. Honest businesses reach it through marketing habit — \"guaranteed savings\", \"risk-free trial\", a headline percentage with the conditions in the next sentence — and the screen reads the headline.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30948",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete the guarantee and the headline number from the message copy. Where the rate is genuinely contractual — a deposit account, an insured product — say what it is and who insures it in the same sentence rather than leading with the figure. Done when no sample makes a promise about money that the business could not be held to in writing.",
      "pitfalls": [
        "Adding \"terms apply\" or a disclaimer link does not settle this: the screen reads the claim, and a disclaimer confirms that the headline overstated it."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-243",
      "slug": "msg-243",
      "title": "No relaying one-time passcodes on behalf of other service providers",
      "statement": "Campaign content must not promote persistent relaying of one-time passcodes for other service providers.",
      "rationale": "A verification code is the most trusted message a consumer receives, and its trust rests entirely on it coming from the service being logged into. A sender relaying codes for other companies breaks that: the recipient cannot tell whose login is being confirmed, and the sending brand cannot be held to the consent because it has no relationship with the recipient. It is also the exact shape traffic-pumping fraud takes, which is why it is refused as a pattern rather than assessed message by message.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Telnyx",
          "code": "40322",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register each business that owns the login under its own brand and campaign so the codes go out under the name the consumer is signing into. Where you are the platform, that means registering on behalf of each customer rather than pooling them under yours. Done when every sample names a service the registered brand actually operates.",
      "notes": "Telnyx 40322 is recorded as \"Permanent — fix content\" in one source and as remediable in another; the strict reading is kept. Telnyx is the only source that publishes this in these words, so the rule is left universal rather than provider-tagged: it is the same consent-chain and sender-identity requirement the whole framework applies, and tagging it Telnyx-only would drop a real obligation for every other route. Twilio and Bandwidth reach the same outcome through the ISV-versus-end-business rules instead.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-245",
      "slug": "msg-245",
      "title": "No fake-prize, impersonation or advance-fee patterns",
      "statement": "Campaign content must not promote fake prizes, impersonated brands and advance-fee solicitations.",
      "rationale": "These three patterns share a mechanism — manufacture a reason the recipient is owed something, then extract a payment or a credential to release it — and Twilio screens them together because they are what its fraud code was written about. They are separated from general deception because they are recognisable by shape rather than by verification: nobody has to establish what the business really does to know that a prize nobody entered for is not real. The registration-time signal is often the brand record itself, where the details do not match anything a search would find.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30959",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any sender identity other than the registered brand, any prize claim with no promotion behind it, and any payment asked for before a benefit is delivered. Where you act for a named principal, say so in the description and register under a brand that matches what the recipient will see. Done when the sender name in every sample is the brand on the registration.",
      "notes": "Twilio 30959 is \"a permanent disqualification from A2P 10DLC messaging\" — the rejection ends the account's route, not just the campaign, so this must never be surfaced as something to edit and resubmit.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-246",
      "slug": "msg-246",
      "title": "No manufactured urgency",
      "statement": "Messages must not manufacture urgency — neither a security pretext such as an account about to be suspended, nor a commercial countdown, scarcity or deadline that is not real.",
      "rationale": "Urgency is the mechanism every SMS scam runs on, because a person who believes they have ninety seconds does not check the sender. Carriers therefore screen for the pattern rather than for the underlying truth, which is why an honest retailer running a fake countdown that resets every night is filtered alongside the phishing traffic it resembles. Twilio names both halves — \"your account will be suspended\" as the security pretext, fake countdowns and false scarcity as the commercial one — and codes them to rejections that are not eligible for resubmission.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30960",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the deadline out unless it is real and you can show when it was set. A genuine sale that ends on Sunday can say so; a countdown that restarts, a \"last chance\" sent monthly, or a stock figure nobody is counting cannot. Where the message concerns a real account event, describe what happened and let the recipient act in their own time rather than telling them what is about to be lost.",
      "pitfalls": [
        "Softening the wording while keeping the mechanism does not settle it — \"ending soon\" on a promotion that never ends is the same finding as a countdown clock."
      ],
      "notes": "Absorbs the catalog's separate artificial-urgency row (MSG-247). The two are one judgement about the same defect: MSG-246 is the security pretext, MSG-247 the commercial one, and a rule that split them would report one message twice.",
      "catalogIds": [
        "MSG-247"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-249",
      "slug": "msg-249",
      "title": "No fraud or scam message content",
      "statement": "Campaign content must not promote fraud or scam content — deception for financial or personal gain.",
      "rationale": "This is the category carriers act on fastest and punish hardest, because a fraudulent text costs a real person real money within minutes of delivery and the carrier is the one who delivered it. T-Mobile grades it Severity-0 Tier 1 at $2,000 per violation, which is per message rather than per campaign, and Twilio describes its Gen-2 code for it as a permanent disqualification from A2P 10DLC. Nothing else in the registry escalates that far, and nothing else attaches to the account rather than to the campaign.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Twilio",
        "Telnyx",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30959",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30884",
          "remediable": false,
          "generation": "gen1"
        },
        {
          "provider": "Telnyx",
          "code": "40322",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "There is no compliant version of this and no reframing to attempt. If the finding is a false positive on genuine transactional copy, the fix is to make the message verifiable rather than to soften it: name the actual transaction, link only to your own domain, and never ask the recipient to send money or credentials by reply.",
      "notes": "The financial exposure is worth stating alongside the rejection: T-Mobile prices Severity-0 Tier 1 phishing and fraud at $2,000 per violation and Tier 2 illegal content at $1,000, assessed per message rather than per campaign. Twilio 30959 is described as permanent and may trigger account-level enforcement beyond the campaign.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-250",
      "slug": "msg-250",
      "title": "No simulated-phishing or security-testing programmes",
      "statement": "Campaign content must not promote simulated phishing and security-awareness test messaging.",
      "rationale": "A simulated phishing text is indistinguishable from a real one to every system that handles it, so it trains carrier filters on the sender, generates the same consumer complaints, and reaches the same people — including employees who left months ago and whoever now holds a recycled number. T-Mobile fines it at Tier 1, the same band as actual phishing, which surprises security teams badly because the intent is defensive and the programme was signed off internally. Intent is not a defence the network can see.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30884",
          "remediable": false,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30007",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move the simulation off SMS. Run the exercise on channels you control end to end — corporate email, an internal app, a managed device — where the deception never touches the public network. Where the awareness programme itself is worth texting about, register it on honest copy: reminders, enrolment and results, with nothing designed to be mistaken for something else.",
      "pitfalls": [
        "Internal authorisation and a signed engagement letter do not change the verdict: the carrier is assessing the traffic on its network, and the employer's consent is not the recipient's."
      ],
      "notes": "T-Mobile prices this at Severity-0 Tier 1, $2,000 per violation — the same band as real phishing, and assessed per message.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-255",
      "slug": "msg-255",
      "title": "No impersonating a third-party brand on the landing page or in a linked file",
      "statement": "Neither the landing page a message reaches nor any file it offers for download may present itself as belonging to a business other than the registered brand.",
      "rationale": "Impersonation moved to the landing page precisely because message text is screened and pages historically were not: the text says \"your delivery\", the page wears a courier's livery, and the recipient hands over card details to something they believe is the courier. CTIA extends the prohibition to linked downloads for the same reason — a PDF invoice on somebody else's letterhead does the same work as a cloned page. Legitimate senders hit this through reseller and white-label arrangements where the page genuinely carries a partner's branding and nobody thought of it as impersonation.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] -> landing page + linked download",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the destination page identify the registered brand above the fold — name, logo and contact route — and remove any third-party branding you are not authorised to display. Where you act for a named principal, say so on the page in the form \"X, an authorised agent of Y\" rather than presenting as Y. Done when the page names the sender before it names anyone else.",
      "pitfalls": [
        "A footer disclaimer does not settle it: reviewers judge what the page presents itself as on arrival, and a logo at the top outweighs a line of small print at the bottom."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-261",
      "slug": "msg-261",
      "title": "The call to action must not misdescribe the programme",
      "statement": "Neither the call to action nor subsequent messages may use deceptive language, or misdescribe what the programme sends, how often, or what it costs.",
      "rationale": "This is deception measured against the promise rather than against the truth: a consumer who agreed to \"occasional order updates\" and receives daily promotions was misled at the point of consent, whichever message you read on its own. It is the one deception rule that reaches back to the opt-in copy, which is why it catches programmes that are entirely honest about each message and dishonest about the shape of the whole. The innocent cause is almost always drift — the CTA was written for the programme as it was launched, and nobody rewrote it when marketing was added.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + CTA copy + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the call to action to describe the programme you are actually registering — every content type it sends, the real frequency, and the cost position — then check the samples against it line by line. Done when a recipient reading only the CTA would be unsurprised by every sample in the campaign.",
      "example": "CTA: \"Text me Acme Coffee offers, rewards updates and order notifications. Msg frequency varies (about 4/month). Msg & data rates may apply.\" — matching a sample set that contains all three kinds.",
      "pitfalls": [
        "Adding the missing content type to the CTA usually changes the consent standard as well: a programme that admits it sends promotions needs promotional-grade consent, so the CTA edit and the use case have to move together."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-262",
      "slug": "msg-262",
      "title": "No sender whose business is buying, selling or sharing consumer information",
      "statement": "Campaign content must not promote the buying, selling or sharing of consumer information as a business model.",
      "rationale": "Every consent in the framework is held by one business for one relationship, so a sender whose business is moving consumer records between parties cannot hold the consent it is relying on — the person agreed to hear from somebody else. Twilio applies this to the sender rather than to the message, and explicitly to non-profits and political committees as well, because list-swapping is how their traffic is built too. That is the surprise: an organisation can have impeccable message copy and still be refused on what it does for a living.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30468",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This is settled on the business model, so the fix is not in the message copy: stop selling or sharing consumer data for other parties' marketing, and make the privacy policy say so in the standard form — mobile information is never sold, rented, traded or shared with third parties or affiliates for their own marketing. Where a vendor genuinely only delivers the messages, name it as a processor rather than as a partner. Done when the policy, the website and the description all describe one business holding one relationship.",
      "pitfalls": [
        "The website and the privacy policy are read alongside the campaign, so a permissive data-sharing clause left in a generated policy fails this even when the business does not actually sell anything."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-263",
      "slug": "msg-263",
      "title": "An abandoned-cart message must carry an opt-out notice",
      "statement": "Every abandoned-cart reminder must tell the recipient how to stop receiving them.",
      "rationale": "The cart reminder is the message most likely to feel like surveillance: it arrives because the recipient looked at something and did not buy it, and it is the one people most want to stop on the spot. CTIA singles it out for a per-message opt-out notice for that reason, rather than letting it rely on the notice in some earlier message of the programme. Retailers omit it in good faith because the reminder is short by design and every character is fighting for the click — which is exactly the pressure the rule exists to overrule.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "cart-reminder message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to opt out\" to the cart-reminder template itself, not only to the welcome message. Done when the reminder carries it on its own, read in isolation, with no earlier message in the thread.",
      "example": "Acme Coffee: you left the Ethiopia Guji in your basket. Finish up at https://acmecoffee.com/cart Reply STOP to opt out.",
      "notes": "Universal rather than tagged, because TCR has no use case for abandoned-cart messaging — it is a shape a MARKETING or MIXED campaign takes, and no fact on the registration declares it. The condition is carried in the criteria, which open with a PASS boundary for every campaign that sends no cart reminders.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-264",
      "slug": "msg-264",
      "title": "An abandoned-cart message must not complete the purchase in the thread",
      "statement": "A cart reminder must not collect payment information, accept purchase approval by keyword reply, or otherwise complete the transaction — the recipient must finish the purchase through a direct URL.",
      "rationale": "A purchase confirmed by replying Y is a charge authorised by a single character, sent from a device somebody else may be holding, with no price, no total and no terms on screen at the moment of authorisation. T-Mobile and CTIA both refuse it, and the harm is not hypothetical: the dispute rate on keyword-confirmed purchases is what put the rule in the handbook. Merchants build it because it converts, and because the platform offering the feature does not mention that carriers refuse the campaign that uses it.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "cart-reminder message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the keyword purchase step with a direct link to the checkout page for that basket, and take every request for card, billing or address detail out of the message body. Done when the only thing a reply can do is stop the messages, and every purchase completes on your own site with the total on screen.",
      "example": "Acme Coffee: your basket is still here — 2 bags of Ethiopia Guji, $34 total. Check out at https://acmecoffee.com/cart/4471 Reply STOP to opt out.",
      "pitfalls": [
        "A \"reply Y to confirm and we will charge your card on file\" flow is exactly the prohibited pattern, and it is the one most cart platforms offer as a headline feature."
      ],
      "notes": "Universal for the same reason as MSG-263: no registration fact declares a cart programme, so the condition lives in the criteria and opens with a PASS boundary.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-266",
      "slug": "msg-266",
      "title": "A forwarded or referred message must say why the recipient received it",
      "statement": "A message sent because another consumer referred or forwarded it must identify the person who sent it and state why the recipient is receiving it.",
      "rationale": "A referral message reaches somebody who never opted in to anything — the consent, such as it is, belongs to their friend — so the only thing standing between it and an unsolicited commercial text is the recipient being able to see immediately that a person they know is behind it. Without the name and the reason it is indistinguishable from spam and is reported as such, which damages the sending number for every other recipient. Businesses omit the name because their referral tooling does not have it, or because the message reads better without it, and neither is a reason the carrier recognises.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "viral message body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the referring person's name and the reason into the first sentence of the referral template, before the offer — a [referrer name] token and a clause saying they sent it. Done when a recipient who has never heard of your business can tell, from the first line, who thought of them and why.",
      "example": "Acme Coffee: [referrer first name] sent you a free bag of our Ethiopia Guji because they thought you would like it. Claim it at https://acmecoffee.com/r/4471 Reply STOP to opt out.",
      "pitfalls": [
        "Naming only the business does not satisfy this — the required identification is of the consumer who sent it, and that is the part referral tooling most often cannot supply."
      ],
      "notes": "Universal rather than tagged: no fact on the registration declares a referral or refer-a-friend programme, so the criteria carry the condition and open with a PASS boundary for campaigns that have none. Note the interaction with the consent rules — a referral message reaches somebody who did not opt in, and satisfying this rule does not settle whether the programme may send it at all.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-267",
      "slug": "msg-267",
      "title": "A political donation programme must confirm intent and certify eligibility",
      "statement": "A message-originated political contribution must be answered with a message confirming the contributor's intent to give and carrying the eligibility certification — at least 18, own funds, not a foreign national, not a federal contractor.",
      "rationale": "Campaign-finance law puts the burden of establishing that a contribution is lawful on the committee receiving it, and a text has none of the surrounding form a web donation page would carry. The reply is where the certification has to live, because it is the only place the contributor can see it before the charge completes. Committees miss it because the message-originated flow feels like a confirmation receipt rather than a legal instrument, so they write it as one — and a receipt that does not ask the contributor to affirm anything leaves the committee unable to show the contribution was lawful.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "political donation MT body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Write the reply to the donation keyword so it does two things in order: restate the amount and ask the contributor to confirm they intend to give it, then set out the four eligibility statements they are certifying by confirming. Done when the reply contains a confirmation step the contributor must answer and all four certifications are legible in the same message.",
      "example": "Riverside Shelter Action Fund: reply YES to confirm your $25 contribution. By replying YES you certify that you are at least 18, a US citizen or lawful permanent resident, using your own funds, and not a federal contractor. Msg & data rates may apply. Reply STOP to opt out.",
      "pitfalls": [
        "Putting the certification behind a link does not satisfy this — the contributor must be able to read what they are certifying in the message they are replying to."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-290",
      "slug": "msg-290",
      "title": "Political content is refused outright on platforms that prohibit it",
      "statement": "A political campaign cannot be registered through an ISV whose own terms prohibit political message content, whatever the carriers allow.",
      "rationale": "Political messaging is registrable at TCR under a dedicated use case with its own vetting, so a committee that clears that vetting reasonably expects to be able to send — and then finds its platform refuses the category as a matter of its own policy. Klaviyo does exactly this. Discovering it after the vetting is complete and the election calendar is running is the expensive way to find out, and there is no appeal because nothing has gone wrong with the registration: the platform simply does not carry this business.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.usecase + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Klaviyo"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ],
        "providers": [
          "KLAVIYO"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL, and the submission target is KLAVIYO.",
      "universal": false,
      "remediation": "Move the programme to a route that carries political traffic. That is a commercial decision only you can make — pick a provider whose acceptable-use policy permits political content, register the brand and campaign there, and budget for the political vetting to be redone against the new route. Nothing in the campaign record needs to change, so do not spend a cycle editing it.",
      "notes": "Off-platform and provider-tagged. What the user has to do themselves: confirm the acceptable-use policy of any replacement platform in writing before rebuilding, because this is a vendor term rather than a carrier rule and no error code or vetting result will warn them. Deliberately not generalised — most routes carry political traffic, and applying Klaviyo's position as a superset would refuse legitimate campaigns everywhere else. Source: prohibited-content.md.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you confirmed in writing that the replacement platform's acceptable-use policy permits political content?",
        "howToCheck": [
          "Read the acceptable-use policy, then get the answer in writing from the vendor. This is a vendor term, so no error code or vetting result will warn you.",
          "Budget for the political vetting to be redone against the new route.",
          "Do not spend a cycle editing the campaign record — nothing in it is the problem."
        ],
        "failureLooksLike": "A committee clears political vetting, expects to send, and finds its platform refuses the category as a matter of its own policy. There is no appeal, because nothing has gone wrong with the registration."
      }
    },
    {
      "id": "MSG-293",
      "slug": "msg-293",
      "title": "Verbal opt-in grants no relief on sample content",
      "statement": "Samples must carry the brand name and opt-out instructions even when consent was collected verbally rather than on a web form.",
      "rationale": "Collecting consent by phone changes what evidence you file, and nothing about what the messages have to say — but the belief that it does is widespread, because the agent already identified the business on the call and already said the customer could stop at any time. The recipient is a different person by the time the text arrives: hours later, looking at an unknown number, with none of the call in front of them. AWS states the requirement explicitly for exactly this reason, and it is the anti-exemption that stops an offline consent method quietly suppressing the two rules every message owes.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "AWS",
          "code": "SAMPLE_MESSAGE_MISMATCH",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Add the registered brand name to the start of every sample and \"Reply STOP to opt out\" to the end, exactly as a web-opt-in campaign would. Done when a sample read cold, with no memory of the call, still says who is texting and how to stop it.",
      "example": "Acme Coffee: your support ticket 4471 is now with a specialist. We will text an update within one business day. Reply STOP to opt out.",
      "pitfalls": [
        "Naming the brand in the verbal script does not discharge this — the script is consent evidence, the samples are message content, and reviewers check them separately."
      ],
      "notes": "Deliberately overlaps XBC-010 and MSG-SAMPLES-OPTOUT, which are universal and already require both elements. It exists as its own rule because the catalog records the anti-exemption separately: an author narrowing either of those to web surfaces would silently exempt verbal campaigns, and this rule is what would go red.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-296",
      "slug": "msg-296",
      "title": "Platform sample specifications must be met where an ISV imposes one",
      "statement": "Where the campaign is registered through an ISV that publishes its own sample specification, the samples must satisfy it — for HubSpot, exactly two samples, each carrying the business name, opt-out instructions including STOP, a privacy-policy link, and bracketed personalisation.",
      "rationale": "An ISV sits between the business and TCR and applies its own acceptance rules first, so a submission that would clear every carrier requirement is still rejected at the platform without ever reaching a carrier. HubSpot publishes the strictest sample specification found anywhere — a superset of what any carrier asks — and businesses meet the carrier bar, submit, and are refused by their own tooling with no reference to a carrier rule they can look up. It is worth stating separately because the fix is cheap and the confusion is expensive: nothing is wrong with the campaign, only with the shape of it for one route.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "HubSpot"
      ],
      "applicability": {
        "providers": [
          "HUBSPOT"
        ]
      },
      "applicabilityText": "Applies when the submission target is HUBSPOT.",
      "universal": false,
      "remediation": "Submit exactly two samples and put all four elements in each: the business name at the front, \"Reply STOP to opt out\", a link to your public privacy policy, and at least one [bracketed] personalisation token. Done when both samples carry all four — HubSpot checks each sample independently rather than the set as a whole.",
      "example": "sample1: \"Acme Coffee: hi [first name], 20% off all espresso blends this weekend. Privacy: https://acmecoffee.com/privacy Reply STOP to opt out.\"\nsample2: \"Acme Coffee: hi [first name], order [order number] is ready for pickup. Privacy: https://acmecoffee.com/privacy Reply STOP to opt out.\"",
      "pitfalls": [
        "The privacy policy behind the link has to carry the mobile-information non-sharing statement and the rates disclosure — a link to a generic generated policy satisfies this rule and is then rejected on the policy itself."
      ],
      "notes": "Provider-tagged rather than applied as a superset, and that is the whole point of the tag here: this is one vendor's contract, not a carrier requirement, so imposing it on everyone would fail compliant registrations for not doing something no carrier asks. The check returns UNCLEAR while the submission target is unknown, because \"we do not know which platform you are using\" is the honest answer and returning a vacuous PASS would hide the requirement from the users who owe it. Source: aws-and-resellers.md section A, HUBSPOT-REG.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-ADULT-DATING",
      "slug": "msg-adult-dating",
      "title": "No adult dating or companionship solicitation",
      "statement": "Campaign content must not promote adult dating, companionship, or hookup services.",
      "rationale": "Dating content marketed on sexual availability falls under the SHAFT sex prohibition and carries an unusually high complaint rate, because recipients frequently did not knowingly opt in. Mainstream relationship services are judged separately on how they present themselves rather than on the category label.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove sexually suggestive framing and restrict messaging to non-explicit service notifications for users who opted in inside your own product. If the service is adult-oriented by nature, 10DLC is not an available channel.",
      "catalogIds": [
        "MSG-165"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-BAIL-BONDS",
      "slug": "msg-bail-bonds",
      "title": "No bail bonds or related solicitation",
      "statement": "Campaign content must not promote bail bonds and related legal-financial solicitation.",
      "rationale": "Bail bond marketing reaches people at a moment of acute distress, frequently using data obtained from arrest records rather than consent. Carriers treat unsolicited outreach on that basis as a per-se harm, independent of the legality of the underlying business.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This solicitation model cannot be registered. Messaging existing clients about their own case is a different category — describe it that way and remove any acquisition content.",
      "catalogIds": [
        "MSG-230"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CANNABIS",
      "slug": "msg-cannabis",
      "title": "No cannabis, CBD, or hemp-derived product content",
      "statement": "Campaign content must not promote cannabis, CBD, hemp, or related products.",
      "rationale": "Cannabis is prohibited regardless of state legality, because where federal and state law diverge the industry applies federal law. This surprises operators in legal states more than any other rule, and Twilio states explicitly that keyword detection alone is insufficient — euphemistic framing is caught and treated the same way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30940",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cannabis-related messaging cannot be registered on 10DLC in the US regardless of state licensing. Do not attempt to re-frame it — euphemism detection is explicit in the vetting criteria.",
      "catalogIds": [
        "MSG-188"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CHARITY-SOLICIT",
      "slug": "msg-charity-solicit",
      "title": "Charitable solicitation requires the charity use case and real status",
      "statement": "Campaign content must not promote charitable donation solicitation outside the charity use case.",
      "rationale": "Donation solicitation gets favourable treatment and correspondingly stricter eligibility, including verified 501(c)(3) status. Soliciting donations under a marketing use case bypasses that check and is rejected on the mismatch rather than on the content.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register under the CHARITY use case with your 501(c)(3) EIN and tax-exempt status declared on the brand, or remove the donation ask from the campaign content.",
      "example": "Entity type: NON_PROFIT · Tax exempt status: 501(c)(3) · Use case: CHARITY — or drop the ask: \"Riverside Shelter: our winter programme opened today. Details: https://riversideshelter.org/winter Reply STOP to opt out.\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-COLLECTIONS-FIRST-PARTY",
      "slug": "msg-collections-first-party",
      "title": "First-party collections messaging must not use third-party framing",
      "statement": "Campaign content must not promote debt collection presented as third-party activity.",
      "rationale": "First-party collection — a business chasing its own invoice — is registrable, while third-party collection is not. Businesses routinely describe their own collections in agency language, which reads as the prohibited category and gets rejected for something they are not doing.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the messaging as first-party — your business, your customer, your invoice — and remove agency framing and any threatening language from the samples.",
      "example": "Acme Coffee: your invoice #4471 for $240 is now 14 days overdue. Pay at https://acmecoffee.com/pay/4471 or reply HELP to reach us. Reply STOP to opt out.",
      "notes": "Retryable rather than a hard stop, because the usual defect is describing legitimate first-party activity badly.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-CRYPTO",
      "slug": "msg-crypto",
      "title": "No cryptocurrency or high-risk investment solicitation",
      "statement": "Campaign content must not promote cryptocurrency, stock alerts, or high-risk investment offers.",
      "rationale": "Investment solicitation over SMS is heavily associated with pump-and-dump and fraud, so carriers refuse the category rather than assess individual senders. A regulated institution servicing existing accounts is judged separately.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30947",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30946",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Investment solicitation cannot be registered. Account servicing for existing customers of a regulated institution is a separate category — frame the campaign that way if it applies.",
      "catalogIds": [
        "MSG-223",
        "MSG-225"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DEBT",
      "slug": "msg-debt",
      "title": "No third-party debt collection, relief, or credit repair content",
      "statement": "Campaign content must not promote third-party debt collection, debt relief, or credit repair services.",
      "rationale": "Third-party debt content combines a regulated activity with a high complaint rate, and carriers refuse it as a category. First-party collection — a business contacting its own customer about its own invoice — is treated differently, which is the distinction that decides whether your campaign is registrable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30943",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30949",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30950",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Third-party debt services cannot be registered. If you are collecting your own receivables from your own customers, say so explicitly in the description — that is a different, registrable category.",
      "notes": "Three Gen-2 codes, one per limb: 30943 third-party collection, 30949 debt reduction or consolidation, 30950 credit repair. All three are \"not eligible for resubmission\", so a rejection under any of them ends the campaign rather than starting a fix loop.",
      "catalogIds": [
        "MSG-218",
        "MSG-220"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DECEPTIVE",
      "slug": "msg-deceptive",
      "title": "Content must not be deceptive, misleading, or impersonating",
      "statement": "Messages must not misrepresent the sender, imitate another brand, or make claims the business cannot support.",
      "rationale": "Deception and impersonation are Severity-0 findings that stop all messaging on the program, and they are the categories carriers escalate fastest because they map directly onto fraud. Unlike most defects, these attract per-message violation fees rather than a rejection you can fix and resubmit.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR",
        "FTC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any content implying an affiliation the business does not have, any urgency claim that is untrue, and any sender identity other than the registered brand.",
      "catalogIds": [
        "MSG-244"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-DESC-QUALITY",
      "slug": "msg-desc-quality",
      "title": "Campaign description must explain who, what, and how they opted in",
      "statement": "The description must state who receives the messages, what those messages contain, and how recipients consented.",
      "rationale": "The description is the first thing a reviewer reads and the basis on which everything else is judged. A description that restates the use-case name gives them nothing to verify against, and is the most frequently cited reason for a campaign-description rejection.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write three things explicitly: the audience, the message content, and the opt-in source. Name the business and the website rather than describing a generic program.",
      "example": "Acme Coffee sends weekly promotional offers, new product announcements, and loyalty rewards updates to customers who opted in via the checkout consent checkbox at acmecoffee.com or in person at our Mission St store. Messages include discount codes and store event invitations.",
      "pitfalls": [
        "\"Marketing messages to our customers\" restates the use case and will be rejected as too vague."
      ],
      "catalogIds": [
        "CMP-033"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-FIELD-SIMILARITY",
      "slug": "msg-field-similarity",
      "title": "Description, message flow, and samples must not be the same text",
      "statement": "The description, message flow, and sample messages must each contain distinct content rather than repeated boilerplate.",
      "rationale": "These three fields answer different questions — what the campaign is, how consent is collected, and what gets sent. Pasting the same paragraph into all three leaves two of those questions unanswered and is explicitly coded as a rejection reason.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.message_flow + campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30911",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write each field to its own purpose: the description explains the program, the message flow explains consent collection, and the samples show real messages.",
      "example": "Description: what the program sends and to whom. Message flow: \"Customers opt in at acmecoffee.com/signup by ticking…\". Samples: the actual texts.",
      "catalogIds": [
        "MSG-030"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-FIREARMS-ADJACENT",
      "slug": "msg-firearms-adjacent",
      "title": "No firearms-adjacent product marketing",
      "statement": "Campaign content must not promote firearms accessories, ammunition, or related product marketing.",
      "rationale": "The firearms prohibition covers ammunition, magazines, and components as well as complete weapons, which surprises retailers who reason that accessories are not weapons. Ranges, training and membership services remain registrable where they sell no product.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove product content of every kind, including accessories and ammunition. Service messaging for ranges, training and memberships remains registrable.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-GAMBLING",
      "slug": "msg-gambling",
      "title": "No gambling or sportsbook content",
      "statement": "Campaign content must not promote gambling, casino, sportsbook, or lottery content.",
      "rationale": "Gambling is banned by some providers and gated behind a special use case by others. The consumer-harm rationale is the same one behind age gating, and the divergence between providers means the safe default is to treat it as blocking.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30944",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm whether your provider offers a gambling or sweepstakes use case before proceeding; several refuse the category outright. Where a sweepstakes use case exists, register under it rather than under marketing.",
      "catalogIds": [
        "MSG-202"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-HIGH-RISK-FINANCIAL",
      "slug": "msg-high-risk-financial",
      "title": "No payday, short-term, or high-interest lending content",
      "statement": "Campaign content must not promote payday loans, short-term lending, or high-interest credit offers.",
      "rationale": "High-risk lending is the category most associated with consumer harm complaints on SMS, and it is coded separately from the ordinary direct-lending attribute. The distinction matters: declaring direct lending is a fixable attribute problem, while promoting payday-style credit is a business-model rejection with no remedy.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This business model cannot be registered. Regulated lenders sending servicing messages to existing customers are a different category — if that is you, remove the acquisition-style offers and set the direct-lending attribute instead.",
      "catalogIds": [
        "MSG-212"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-ILLEGAL",
      "slug": "msg-illegal",
      "title": "No content promoting illegal activity",
      "statement": "Campaign content must not promote illegal goods, services, or activity.",
      "rationale": "Promotion of illegal activity is a Severity-0 finding carrying \"cease all messaging\" rather than a rejection, and it is assessed against federal law even where a state permits the activity. The scope includes the landing page and any downloadable artifact, not only the message body.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered on any channel. Where the activity is state-legal but federally prohibited, federal law is the standard applied.",
      "catalogIds": [
        "MSG-256",
        "MSG-257"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-JOB-LEADGEN",
      "slug": "msg-job-leadgen",
      "title": "No third-party job-board or staffing lead generation",
      "statement": "Campaign content must not promote third-party job alerts or staffing lead generation.",
      "rationale": "Job-alert messaging is a classic lead-generation shape: the consumer opted in on an aggregator and the messages come from employers or agencies they never heard of. Where the employer collected the consent directly, the same content is registrable — the distinction is who holds the relationship.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Restrict messaging to candidates who opted in directly with the sending entity about that entity's own roles. Aggregated or purchased candidate lists cannot be registered.",
      "catalogIds": [
        "MSG-237"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-LEAD-GEN",
      "slug": "msg-lead-gen",
      "title": "No third-party lead generation or multi-level marketing",
      "statement": "Campaign content must not promote third-party lead generation, affiliate marketing, or MLM recruitment.",
      "rationale": "Lead generation depends on messaging people whose consent was collected by someone else, which is the transfer of consent the whole framework prohibits. MLM recruitment is refused for the same structural reason plus its complaint profile.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30468",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered. If you run your own referral programme and collected the consents yourself, describe it that way and make clear no third-party list is involved.",
      "catalogIds": [
        "MSG-234",
        "MSG-238",
        "MSG-239"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-PHARMA",
      "slug": "msg-pharma",
      "title": "No prescription drug or controlled-substance promotion",
      "statement": "Campaign content must not promote prescription drugs, controlled substances, or unapproved health products.",
      "rationale": "Promoting prescription medication over SMS raises both regulatory and safety exposure, and carriers refuse the category. Pharmacy operations — refill reminders and pickup notices to existing patients — remain registrable, and confusing the two is the common failure.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30941",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove promotional drug content. Reframe as operational patient messaging — refills, pickups, appointments — which is registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-194",
        "MSG-196"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-PHISHING",
      "slug": "msg-phishing",
      "title": "Content must not solicit credentials or sensitive data",
      "statement": "Messages must not ask recipients for passwords, full card numbers, SSNs, or account credentials.",
      "rationale": "Credential solicitation over SMS is indistinguishable from smishing from the carrier’s side, and it is filtered and fined accordingly. Legitimate businesses do not need it — verification belongs behind an authenticated session, not in a text reply.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any request for credentials or full financial identifiers. Link to an authenticated page on your own domain instead of collecting data by reply.",
      "catalogIds": [
        "MSG-248"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-REAL-ESTATE-WHOLESALE",
      "slug": "msg-real-estate-wholesale",
      "title": "No unsolicited property-acquisition or wholesaling outreach",
      "statement": "Campaign content must not promote real-estate wholesaling and unsolicited property-acquisition outreach.",
      "rationale": "\"We want to buy your house\" texting is sent to property owners from public records with no consent whatsoever, and it is one of the highest-complaint categories on the network. Ordinary real-estate practice with consenting clients is unaffected.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Messaging property owners sourced from public records cannot be registered — there is no consent to point at. Restrict to clients who opted in directly with your brokerage.",
      "catalogIds": [
        "MSG-232",
        "MSG-233"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-DISTINCT",
      "slug": "msg-samples-distinct",
      "title": "Sample messages must be materially different from one another",
      "statement": "Each sample must illustrate a genuinely different message the campaign will send, not a near-duplicate of another sample.",
      "rationale": "The point of multiple samples is to show a reviewer the range of what will actually be sent. Three variations on the same sentence demonstrate nothing about the rest of the program, so reviewers read near-duplicates as padding to satisfy a field count and reject the campaign as unrepresentative. Exact-duplicate detection catches only the laziest version of this; the common failure is three messages that differ by a product name.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace near-duplicate samples with messages covering genuinely different scenarios from the same campaign — for a marketing campaign, one promotion, one product launch, one loyalty or event message rather than three discount offers.",
      "example": "1. \"Acme Coffee: 20% off all beans this weekend. Shop https://acmecoffee.com/sale Reply STOP to opt out, HELP for help.\"\n2. \"Acme Coffee: your rewards balance is 240 points — enough for a free drink. Reply STOP to opt out.\"\n3. \"Acme Coffee: our Mission St store opens Saturday at 8am, first 50 customers get a free pastry. Reply STOP to opt out.\"",
      "pitfalls": [
        "Changing only a discount percentage or a product name between samples is not material difference.",
        "Do not solve this by making one sample an opt-out confirmation — samples must show campaign content."
      ],
      "catalogIds": [
        "MSG-029"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-NOT-ONLY-ADMIN",
      "slug": "msg-samples-not-only-admin",
      "title": "Samples must show campaign content, not only opt-out or help replies",
      "statement": "The sample set must include actual campaign messages, not exclusively confirmations, HELP replies, or opt-out acknowledgements.",
      "rationale": "Administrative auto-replies are required separately and are configured in their own fields. A sample set made only of them shows a reviewer nothing about what the campaign actually sends, which is the one thing samples exist to demonstrate.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace administrative samples with real campaign messages. Put the HELP and STOP replies in the dedicated auto-reply fields where they belong.",
      "example": "Acme Coffee: your table for 4 on Fri 7pm is confirmed. Reply STOP to opt out, HELP for help.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-OPTOUT",
      "slug": "msg-samples-optout",
      "title": "At least one sample must show opt-out language",
      "statement": "Sample messages must demonstrate that recipients are told how to stop receiving messages.",
      "rationale": "Reviewers use the samples to confirm the opt-out instruction reaches consumers in the actual message body, not just in the registration paperwork. Where every sample omits it, the reasonable inference is that live traffic omits it too.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "CTIA",
        "Bandwidth",
        "Plivo",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "8100",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add \"Reply STOP to opt out\" to your samples. Include it in every promotional sample; for transactional programs at minimum the first message in a series.",
      "example": "Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "MSG-047"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-REPRESENTATIVE",
      "slug": "msg-samples-representative",
      "title": "Sample messages must be realistic, not placeholders",
      "statement": "Samples must be actual messages the campaign will send, with real brand and content specifics.",
      "rationale": "Reviewers compare your samples against your website and use case to decide whether the program is what it claims to be. Generic filler makes that impossible, and it correlates strongly with campaigns that later send something entirely different — which is why it is treated as a signal of bad faith rather than a formatting problem.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite each sample as a message you would genuinely send tomorrow, naming your real brand, real products or services, and a real destination URL on your own domain.",
      "example": "Acme Coffee: your order #4471 is ready for pickup at our Mission St store until 6pm today. Reply STOP to opt out, HELP for help.",
      "catalogIds": [
        "MSG-022",
        "MSG-024"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SAMPLES-USECASE",
      "slug": "msg-samples-usecase",
      "title": "Sample messages must match the declared use case",
      "statement": "The content of the samples must be consistent with the campaign use case selected.",
      "rationale": "The use case determines the consent standard, the throughput a carrier grants, and the filtering applied to your traffic. A campaign registered as ACCOUNT_NOTIFICATION that sends promotions is receiving treatment it did not qualify for, so the mismatch is a registration integrity problem rather than a labelling error.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[] + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Bandwidth",
          "code": "8200",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either change the use case to match what you actually send, or replace the off-pattern samples. If you genuinely send both promotional and transactional content, select MIXED and show both — but note that MIXED requires promotional-grade consent.",
      "example": "For ACCOUNT_NOTIFICATION: \"Acme Coffee: your payment of $24.50 was received. View receipt: https://acmecoffee.com/r/4471 Reply STOP to opt out.\"",
      "pitfalls": [
        "A single promotional sample inside an otherwise transactional set is enough to trigger this — Bandwidth codes it separately as 8200."
      ],
      "catalogIds": [
        "MSG-141"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SEO-SPAM",
      "slug": "msg-seo-spam",
      "title": "No unsolicited agency or SEO service pitching",
      "statement": "Campaign content must not promote unsolicited SEO, web design, or marketing agency solicitation.",
      "rationale": "Cold agency pitching to businesses scraped from directories is unsolicited commercial messaging with no consent behind it, and it is one of the most frequently reported patterns on the network. Messaging clients who engaged you is entirely different.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cold B2B prospecting by SMS cannot be registered — the recipients never consented. Restrict messaging to clients who opted in with you directly.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-ALCOHOL",
      "slug": "msg-shaft-alcohol",
      "title": "Alcohol content requires robust age gating",
      "statement": "Campaign content must not promote alcohol sales or promotion.",
      "rationale": "The A in SHAFT, and the letter that genuinely has a path. Alcohol sits in a contested band: Bandwidth, Bird and Telnyx accept it behind a date-of-birth age gate, Twilio kills the rejected campaign but takes a new one once the gate exists. Because the strictest authority treats the submitted campaign as terminal we surface it as blocking, but the fix is real, and telling an operator otherwise costs them a legitimate programme.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30955",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "706",
          "remediable": true
        },
        {
          "provider": "Bird",
          "code": "706",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Build a 21+ age gate that collects a full date of birth — day, month and year typed in, not a yes/no prompt and not a tick-box — on the opt-in flow and on every website a message links to, then set the age-gated attribute on the campaign. Done when a visitor who enters an under-21 date is refused and cannot proceed by going back. Expect to file a NEW campaign rather than edit this one: Twilio treats the rejected campaign as spent even though it advises building exactly this gate.",
      "pitfalls": [
        "A gate that only appears on the home page does not settle it — the requirement runs to every linked destination, and reviewers test the deep link from the sample message rather than the site root.",
        "Age gating answers who may see the offer and nothing about what the offer says: copy that sells drinking to excess is judged separately by MSG-177 and the gate does not touch it."
      ],
      "notes": "Providers genuinely diverge, and the divergence is in the remediability rather than the prohibition: Twilio 30955/30956 are non-resubmittable for the rejected campaign while Bandwidth DCA2 706, Telnyx 706 and Bird 706 all accept a resubmission once a DOB gate exists, and Twilio toll-free 30456 is conditional on 21+ with full date-of-birth collection. Strictest reading kept for the verdict, the workable path kept in the remediation.",
      "catalogIds": [
        "MSG-178"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-FIREARMS",
      "slug": "msg-shaft-firearms",
      "title": "No firearms sales or promotion",
      "statement": "Campaign content must not promote firearms, ammunition, or weapon parts.",
      "rationale": "The F in SHAFT, and the letter where the published guidance most tempts you into the wrong fix. T-Mobile, Verizon, Bandwidth and Bird all treat firearms as age-gateable, so a dealer reads the codes, builds the gate, and is then refused anyway by Twilio and AWS, which ban the category outright with no path. Under the strict-superset rule the ban is what the registry applies, which means the age gate is wasted work here even though it is the correct answer one letter over.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30957",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30457",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "702",
          "remediable": true
        },
        {
          "provider": "Bird",
          "code": "702",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not build an age gate for this: unlike alcohol, gating does not open a path at the strictest providers and Twilio publishes no conditional route on short code, toll-free or long code. Remove product content of every kind — complete firearms, ammunition, magazines and components — and register on services instead: range bookings, training and safety courses, membership renewals and event logistics are registrable when nothing is offered for sale. Done when no message, no description sentence and no linked page names a product with a price.",
      "pitfalls": [
        "Accessories are inside the category, not outside it — optics, magazines and ammunition are refused on the same code as the weapon, which is what catches retailers who moved their copy to \"gear\" and resubmitted."
      ],
      "notes": "Discharges the catalog's remediation constraint (MSG-185) as well as the content prohibition — the withheld age-gate path is the substance of that row. MSG-185 is recorded as Twilio-conditional in the catalog; the rule is left universal because the catalog's own reconciliation resolves the divergence to the ban, and a provider tag here would hand the age-gate path back to everyone who is not on Twilio.",
      "catalogIds": [
        "MSG-181",
        "MSG-185"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-HATE",
      "slug": "msg-shaft-hate",
      "title": "No hate speech",
      "statement": "Campaign content must not promote hate speech or content attacking a protected group.",
      "rationale": "The H in SHAFT. This is one of the few categories where carriers act on a single message rather than a pattern, because the reputational exposure of carrying it is immediate. It is also the letter with the most uniform treatment — every provider that publishes a code refuses without condition, so unlike alcohol or tobacco there is no provider to shop for and nothing to build.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30954",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "705",
          "remediable": false
        },
        {
          "provider": "Infobip",
          "code": "7012",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the content entirely. There is no compliant framing, no age gate and no use case that carries it, and no provider offers a conditional path — so unlike the alcohol and tobacco letters there is nothing to build and nothing to confirm with your provider first.",
      "notes": "Twilio codes hate and violence to one Gen-2 code (30954), so a rejection under it does not say which fired. MSG-170 holds the violence side and MSG-171 the hostility aimed at the recipient.",
      "catalogIds": [
        "MSG-168"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-SEX",
      "slug": "msg-shaft-sex",
      "title": "No adult or sexually explicit content",
      "statement": "Campaign content must not promote sexually explicit or adult content.",
      "rationale": "The S in SHAFT, and the one letter with no path at all. Carriers treat adult content as unsuitable for a channel with no age verification at the transport layer, and Twilio codes it as not eligible for resubmission. Operators arrive here expecting the alcohol treatment — build an age gate, come back — and that expectation is the expensive part: acting on it burns a campaign that cannot be recovered, on top of the build.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "703",
          "remediable": false
        },
        {
          "provider": "Telnyx",
          "code": "703",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not build an age gate for this — it is not the fix, and no amount of verification makes adult content registrable on 10DLC under any use case. The only thing that clears the finding is that the content is not adult: if the campaign is a clinical or sexual-health service being read as adult, say who the recipients are and what the service does in the terms a clinician would use. Otherwise adult programmes need a different channel and a different carrier relationship entirely.",
      "pitfalls": [
        "Bandwidth's Proxy path and Infobip both publish a conditional age-gate code for sexual content, which reads as a route and is not one under the strict reading — spending a build cycle on it costs you the campaign at Twilio and gains nothing anywhere else."
      ],
      "notes": "Discharges the catalog's remediation constraint (MSG-167) as well as the content prohibition: the rule exists as much to withhold the age-gate path as to detect the category. Divergence recorded — Bandwidth Proxy 7006 and Infobip 7006 are conditional on a robust age gate, while Twilio 30953, Bandwidth DCA2 703, Bird 703, Telnyx 703 and AWS all refuse outright. Canonical resolution is terminal.",
      "catalogIds": [
        "MSG-162",
        "MSG-167"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SHAFT-TOBACCO",
      "slug": "msg-shaft-tobacco",
      "title": "No tobacco, vape, or nicotine product content",
      "statement": "Campaign content must not promote tobacco, vaping, or nicotine products.",
      "rationale": "The T in SHAFT, and the letter that splits in two. Twilio permits tobacco on short code behind an age gate and never on long code or toll-free — which is to say never on 10DLC — while vape, e-cigarettes and e-liquid are refused on every channel including the one that carries tobacco. Retailers who sell both read the tobacco allowance and assume it covers the vape wall, and that assumption is what the rejection is usually about.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30958",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30458",
          "remediable": false
        },
        {
          "provider": "Bandwidth",
          "code": "707",
          "remediable": true
        },
        {
          "provider": "Telnyx",
          "code": "707",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Take the product out of the 10DLC campaign. The age gate that rescues alcohol reaches tobacco only on short code, which is a different registration on a different number type — and it does not reach vape, e-cigarettes, e-liquid or pods on any channel at all, so separating the vape lines out is the first thing to check. Cessation and nicotine-replacement programmes are a distinct category and stay registrable when the copy is about quitting rather than about the product.",
      "pitfalls": [
        "Splitting the vape products into a second campaign does not help — the vape ban is by channel, not by campaign, so the second campaign is refused on the same code."
      ],
      "notes": "Bandwidth (\"Alcohol and Tobacco can be supported with robust age-gating and proper opt-in\" on 10DLC), Telnyx 707 and Bird 707 all accept tobacco behind a date-of-birth gate, so this is a genuine divergence rather than a strictness difference. Strictest reading kept; the vape half is uniform across every source.",
      "catalogIds": [
        "MSG-180"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SPAM-STYLE",
      "slug": "msg-spam-style",
      "title": "Messages must not use spam-signal formatting",
      "statement": "Messages must avoid all-caps shouting, excessive punctuation, and obfuscated wording used to evade filters.",
      "rationale": "Carrier filters score message shape as well as content, so these patterns reduce deliverability even on an approved campaign. Character substitution and spacing tricks are treated as deliberate filter evasion, which escalates from filtering to enforcement.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write samples in ordinary sentence case with normal punctuation, and remove any character substitution.",
      "example": "Acme Coffee: 20% off all beans this weekend. Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "\"FR€E\" or \"C.A.S.H\" reads as deliberate filter evasion, which is worse than the word it hides."
      ],
      "catalogIds": [
        "MSG-126",
        "MSG-127"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-SWEEPSTAKES-DECEPTIVE",
      "slug": "msg-sweepstakes-deceptive",
      "title": "No prize or winner claims without a genuine promotion",
      "statement": "Campaign content must not promote prize, winner, or sweepstakes claims not backed by a real promotion.",
      "rationale": "A \"you have won\" message with no underlying promotion is straightforward deception and is filtered as fraud rather than as marketing. Genuine sweepstakes are registrable under the sweepstakes use case with the required official rules.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove prize claims unless a real promotion exists with published official rules. Where it does, register under the sweepstakes use case and link the rules from the message.",
      "catalogIds": [
        "MSG-207"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-TELEHEALTH-ACQUISITION",
      "slug": "msg-telehealth-acquisition",
      "title": "No telehealth or supplement acquisition marketing",
      "statement": "Campaign content must not promote telehealth acquisition marketing and unproven supplement claims.",
      "rationale": "Health-outcome marketing over SMS combines regulatory exposure with a high rate of overstated claims, and weight-loss and hormone offers in particular are screened aggressively. Clinical operations messaging to existing patients is registrable and should be framed that way.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove acquisition and outcome-claim content. Patient operations messaging — appointments, refills, results availability — is registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-251"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-TIMESHARE",
      "slug": "msg-timeshare",
      "title": "No timeshare or vacation-club solicitation",
      "statement": "Campaign content must not promote timeshare, vacation club, or holiday-ownership solicitation.",
      "rationale": "Timeshare marketing has a long enforcement history for high-pressure and deceptive tactics, and carriers screen it as a category. Ordinary travel and hospitality messaging is unaffected, so the line is the ownership-sales pitch rather than the industry.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.description + campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "TCR",
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove ownership-sales content. Reservation, itinerary and stay-service messaging to your own customers remains registrable under CUSTOMER_CARE or ACCOUNT_NOTIFICATION.",
      "catalogIds": [
        "MSG-231"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-URL-OWNERSHIP",
      "slug": "msg-url-ownership",
      "title": "Links must resolve to a domain the brand owns",
      "statement": "URLs in messages must point at the brand’s own domain or a dedicated branded short domain, and must identify the owner.",
      "rationale": "Public shorteners hide the destination, so carriers cannot assess where a message leads and block them categorically. The standard is stricter than \"avoid bit.ly\": the shortener must be dedicated to the sender, and the destination must identify the business, because shared redirect infrastructure is what makes a link untraceable.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30892",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30963",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use full URLs on your own domain, or a short domain dedicated exclusively to your business. Confirm the destination page identifies the business by name and carries contact details.",
      "example": "Shop the sale: https://acmecoffee.com/sale  —  or a dedicated short domain such as https://acme.link/sale",
      "pitfalls": [
        "A branded short domain on shared redirect infrastructure still fails the dedicated-IP requirement.",
        "The same defect is remediable under the old code and NOT resubmittable under the new one — assume the strict reading."
      ],
      "catalogIds": [
        "MSG-094",
        "MSG-099",
        "MSG-100"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "MSG-VARIABLES",
      "slug": "msg-variables",
      "title": "Template variables must use square-bracket notation",
      "statement": "Personalisation placeholders in samples should use [square brackets] rather than code syntax.",
      "rationale": "Square brackets are the convention reviewers expect, and they make clear which parts vary at send time. Raw code syntax reads as an unfinished integration and invites the reviewer to treat the sample as a placeholder rather than a real message.",
      "layer": "MESSAGE_CONTENT",
      "layerSlug": "message-content",
      "object": "campaign.sample[]",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Convert placeholders to square-bracket notation and give them readable names describing the value.",
      "example": "Acme Coffee: hi [first name], your order [order number] ships today. Reply STOP to opt out.",
      "pitfalls": [
        "Bracket placeholders belong in samples only — never send them in live traffic."
      ],
      "catalogIds": [
        "MSG-025"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-026",
      "slug": "ops-026",
      "title": "The universal opt-out keyword set must be declared",
      "statement": "STOP, END, CANCEL, UNSUBSCRIBE and QUIT must all appear in the declared opt-out keyword set.",
      "rationale": "This five-word set is honoured at handset and carrier level regardless of what you configure, so a consumer using any of them has effectively opted out whether your platform noticed or not. Declaring them aligns your suppression list with what has already happened.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare at least the universal five, and preferably the full seven-word FCC per-se set including REVOKE and OPTOUT.",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "notes": "Discharges catalog CMP-184 (\"optoutKeywords is populated and retains STOP\") together with OPS-027, which owns the empty-list case. The split is deliberate: this rule returns UNCLEAR rather than FAIL on an empty list so that a campaign with no keywords yet is reported once, by OPS-027, instead of twice.",
      "catalogIds": [
        "CMP-184"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-027",
      "slug": "ops-027",
      "title": "Opt-out keyword handling must be declared, not assumed",
      "statement": "The campaign must declare an opt-out keyword set, and that set must be the one the sending platform is configured to honour.",
      "rationale": "We cannot observe runtime STOP handling before a campaign exists, but we can observe whether anything was declared at all. An empty or single-keyword declaration is the reliable predictor of a platform that honours only STOP, which is narrower than the law now requires.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.optout_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare the full keyword set your platform honours, then verify in the platform that each one actually triggers an opt-out. The declaration and the configuration must match.",
      "example": "STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE",
      "notes": "Registration-side twin of the runtime obligation. Verifies the declaration, never the behaviour.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-029",
      "slug": "ops-029",
      "title": "A revocation must survive punctuation, case and surrounding words",
      "statement": "Opt-out keywords must be matched case-insensitively, tolerantly of punctuation, and when the keyword is surrounded by other text in the same message.",
      "rationale": "Consumers do not send bare keywords. They send \"Stop.\", \"STOP!!\", \"stop texting me\" and \"you have the wrong number, stop\" — and an exact-match handler treats every one of those as an ordinary reply, so the messages keep arriving from someone who plainly asked them not to. The FCC treats de minimis variance as a revocation regardless of what the handler did, which means the gap between the two is not a bug in the product, it is a violation already in progress.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "inbound message normalisation",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "CTIA",
        "T-Mobile",
        "Aerialink"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Normalise the inbound body before matching: upper-case it, strip punctuation and whitespace, then look for any declared keyword as a whole word anywhere in the message rather than as the entire message. Done when \"Stop.\", \"please STOP\" and \"wrong number, stop\" all suppress the number in a test send.",
      "example": "Inbound \"Please stop texting me!\" → normalised \"PLEASE STOP TEXTING ME\" → matches STOP → suppressed.",
      "pitfalls": [
        "Matching a keyword as a substring rather than as a whole word turns \"stopwatch giveaway\" into an opt-out; match on word boundaries."
      ],
      "notes": "Nothing at registration time can settle this — the handler is code on the sending platform. What the user has to do is test it: send those four variants to their own number after launch and confirm each one lands in the suppression list. OPS-026 and OPS-027 check the declared keyword set, which is a different question from how the set is matched.",
      "catalogIds": [
        "OPS-030"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do \"Stop.\", \"STOP!!\", \"please STOP\" and \"wrong number, stop\" each suppress your own number in a live test?",
        "howToCheck": [
          "Send all four variants to your own programme after launch, from a real handset.",
          "Confirm each one lands in the suppression list — not just the bare keyword.",
          "Check the matcher uses word boundaries, so \"stopwatch giveaway\" is not read as an opt-out."
        ],
        "failureLooksLike": "An exact-match handler files every one of those as an ordinary reply and the messages keep arriving. The FCC treats de minimis variance as a revocation regardless, so the gap is a violation already in progress."
      }
    },
    {
      "id": "OPS-031",
      "slug": "ops-031",
      "title": "A plainly-worded opt-out counts even with no keyword in it",
      "statement": "Revocations phrased in ordinary language — \"take me off your list\", \"please opt me out\" — must be honoured even though they contain no declared keyword.",
      "rationale": "The federal rule is that a revocation is effective if a reasonable person would understand it as one, not if it matches a word list. So a consumer who writes \"I never signed up for this, please remove me\" has revoked, and a keyword-only handler files the message as an ordinary reply and keeps sending. It is the most common route to a complaint that the brand genuinely did not see coming, because the operator can point at a suppression list that never received anything.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "inbound message intent classification",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "CTIA",
        "T-Mobile",
        "AT&T",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Route every inbound message that did not match a keyword through an intent check — a classifier, or a human queue if the volume is small — and suppress on anything a reasonable person would read as \"stop\". Done when a message with no keyword in it can still remove somebody from the list without an agent deciding to be helpful.",
      "example": "Inbound \"hi, I never signed up for these, can you take me off?\" → no keyword match → intent = revocation → suppressed, and the raw text kept on the opt-out record.",
      "notes": "Undecidable before traffic exists and not settleable by configuration alone. What the user has to establish is who or what reads the non-keyword inbound messages, and how fast — an unread inbox is the same as no handler. OPS-056 covers keeping the raw text, which is what later shows the message was understood.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Who or what reads the inbound messages that matched no keyword, and how quickly?",
        "howToCheck": [
          "Name the classifier or the human queue. An unread inbox is the same as no handler.",
          "Test it: send \"take me off your list\" with no keyword in it and confirm the number is suppressed without an agent deciding to be helpful."
        ],
        "failureLooksLike": "\"I never signed up for this, please remove me\" is filed as an ordinary reply. The operator can point at a suppression list that never received anything, and the complaint is the first anyone hears of it."
      }
    },
    {
      "id": "OPS-032",
      "slug": "ops-032",
      "title": "A help request containing a stop-word is not an opt-out",
      "statement": "An inbound message whose intent is a question must not be treated as a revocation merely because it contains a stop-word.",
      "rationale": "This is the false-positive edge of the previous rule, and it is worth its own line because over-correcting is its own harm: \"I cannot get my device to stop buzzing, can you help?\" is a customer asking for support, and silently unsubscribing them loses the notifications they wanted and answers nothing. Teams tune aggressively for recall after a complaint and create this second failure a month later.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "inbound message intent classification",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the intent check is uncertain, prefer the interpretation the consumer can correct: answer the question and ask whether they also want to stop, rather than suppressing silently. Done when a support question containing the word \"stop\" produces a reply rather than a silent unsubscribe.",
      "example": "Inbound \"how do I stop the duplicate texts, is something broken?\" → question, not revocation → HELP response plus an explicit offer to unsubscribe.",
      "notes": "The judgement lives in a classifier we cannot see. What the user has to decide is which way their handler errs and whether the consumer is told — a silent suppression is invisible to everyone, so the safe design is to act and say so.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a support question containing the word \"stop\" produce a reply rather than a silent unsubscribe?",
        "howToCheck": [
          "Send \"I can't get my device to stop buzzing, can you help?\" to your own programme.",
          "Confirm it is answered. Where the intent check is uncertain, the safe design is to act and say so — answer the question and ask whether they also want to stop."
        ],
        "failureLooksLike": "A team tunes aggressively for recall after a complaint and creates the opposite failure a month later: customers silently unsubscribed from notifications they wanted, and their question unanswered."
      }
    },
    {
      "id": "OPS-033",
      "slug": "ops-033",
      "title": "STOP must never come back as an error",
      "statement": "A consumer who sends STOP must never receive an error message in response.",
      "rationale": "An error reply tells the consumer their opt-out failed, so they try again, then complain — and the carrier sees a subscriber who asked to stop twice and got told the system was broken. It is almost always accidental: STOP arrives on a number whose auto-reply is misconfigured, or outside the hours a handler runs, and the platform returns its generic failure text instead of the confirmation.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "STOP response handler",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "MMA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the opt-out path independent of everything else in the handler: suppress first, then reply with the confirmation, and never let a downstream failure produce an error to the consumer. Done when STOP is answered correctly even with the campaign paused, the API key rotated or the CRM unavailable.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "notes": "Not observable from a registration. The user has to test the failure paths themselves — send STOP while the campaign is paused, and again outside business hours — because the correct response in the happy path proves nothing about the ones that matter.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is STOP still answered correctly with the campaign paused, the API key rotated, or the CRM unavailable?",
        "howToCheck": [
          "Test the failure paths, not the happy path: send STOP while the campaign is paused, and again outside business hours.",
          "Confirm the handler suppresses first and replies second, so no downstream failure can produce an error to the consumer."
        ],
        "failureLooksLike": "The platform returns its generic failure text. The consumer believes their opt-out failed, tries again, and complains — and the carrier sees somebody who asked to stop twice and was told the system was broken."
      }
    },
    {
      "id": "OPS-034",
      "slug": "ops-034",
      "title": "A programme in another language still owes the English keyword",
      "statement": "A programme messaging in a language other than English must honour STOP in that language, and must not return an error to the English keyword.",
      "rationale": "A Spanish-language programme whose handler only knows English leaves its subscribers with no working opt-out at all, which is the worst version of this failure. The mirror case is just as real: handsets, carriers and habit mean somebody will send STOP even to a Spanish programme, and answering that with an error is the defect the previous rule describes.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-locale keyword table",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "MMA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the local-language revocation words to the keyword table alongside the English set rather than instead of it, and keep the confirmation in the language the consumer wrote in. Done when both ALTO and STOP suppress on the same programme.",
      "example": "Keyword table for a Spanish programme: STOP, ALTO, PARAR, CANCELAR, plus the standard English set.",
      "notes": "Only the sending platform holds the keyword table. What the user has to confirm is that adding a locale did not replace the default set, which is how most platforms model it.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do both ALTO and STOP suppress on the same programme?",
        "howToCheck": [
          "Send the local-language revocation word and the English one to the same programme.",
          "Confirm adding a locale did not replace the default keyword set — that is how most platforms model it.",
          "Check the confirmation comes back in the language the consumer wrote in."
        ],
        "failureLooksLike": "A Spanish-language programme whose handler only knows English leaves its subscribers with no working opt-out at all — and a subscriber who sends the English keyword out of habit gets an error."
      }
    },
    {
      "id": "OPS-035",
      "slug": "ops-035",
      "title": "The mandatory keywords must work over MMS too",
      "statement": "STOP, HELP and the rest of the mandatory keywords must be processed identically whether they arrive as SMS or MMS.",
      "rationale": "A consumer replying to a picture message often replies as MMS without knowing it, and many platforms route the two protocols through different inbound handlers — one of which was built for keywords and one of which was built for media. The opt-out then works or does not depending on what the consumer was replying to, which is invisible to everybody until the complaint.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "inbound handler, per protocol",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "attributes": {
          "mmsEnabled": true
        }
      },
      "applicabilityText": "Applies when `mmsEnabled` is true.",
      "universal": false,
      "remediation": "Point both inbound paths at the same keyword handler rather than duplicating the logic, and test an opt-out sent as a reply to an MMS. Done when the same reply suppresses whether or not it carries an attachment.",
      "example": "Reply STOP to an MMS promotion → same handler, same suppression, same confirmation as an SMS reply.",
      "notes": "Two code paths, neither of them visible here. The user has to send the test themselves: reply STOP to one of their own MMS sends and confirm the number is suppressed.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does replying STOP to one of your own MMS sends suppress the number?",
        "howToCheck": [
          "Send yourself an MMS from the programme and reply STOP to it.",
          "Confirm the same reply works with and without an attachment — many platforms route SMS and MMS through different inbound handlers."
        ],
        "failureLooksLike": "The opt-out works or does not depending on what the consumer was replying to. That is invisible to everybody until the complaint."
      }
    },
    {
      "id": "OPS-037",
      "slug": "ops-037",
      "title": "On a shared address, STOP must stop everything",
      "statement": "Where several programmes share one application address, STOP must terminate every one of the consumer's active programmes on it, and any keyword menu must offer a STOP ALL option.",
      "rationale": "From the consumer's side there is one number sending them things, and stopping it means stopping it. A registry keyed per programme instead honours the opt-out for whichever one they last received and leaves the others running, so the messages keep coming from the same number they just unsubscribed from — which reads as deliberate evasion even when it is a data model.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "subscription registry keyed by number and application address",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Key suppression on the pair of consumer number and application address, not on the programme, so one STOP clears every subscription on that address. Where a menu offers per-programme choices, put STOP ALL in it. Done when a subscriber to two programmes on one short code receives nothing after a single STOP.",
      "example": "Keyword menu: \"Reply 1 to stop offers, 2 to stop order updates, or STOP ALL to stop everything.\"",
      "notes": "Applies to shared short codes and multi-programme addresses, which no registration field identifies. What the user has to establish is whether their address carries more than one programme, and if so, how their platform scopes suppression — per programme is the common default and it is the wrong one.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does this application address carry more than one programme — and if so, does one STOP clear all of them?",
        "howToCheck": [
          "List the programmes sending from the address.",
          "Ask your platform how suppression is scoped. Per programme is the common default and it is the wrong one.",
          "Test it: subscribe to two programmes on the address, send one STOP, and confirm neither sends again."
        ],
        "failureLooksLike": "The messages keep coming from the same number the consumer just unsubscribed from. It reads as deliberate evasion even when it is only a data model."
      }
    },
    {
      "id": "OPS-038",
      "slug": "ops-038",
      "title": "Opt-out must not be reduced to one prescribed method",
      "statement": "Published opt-out instructions must not designate an exclusive revocation method — a consumer may use any reasonable channel.",
      "rationale": "The FCC rule is that a revocation counts however it reasonably arrives: a reply, a phone call, an email to an address the brand publishes. Copy saying \"the only way to unsubscribe is to reply STOP\" is not just narrower than the law, it is the sentence a plaintiff quotes — it shows the brand telling consumers their email revocation would not work, and it turns every revocation taken on another channel into an admission. Almost nobody writes it deliberately; it comes from terms boilerplate that names the mechanism the platform implements.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + SMS terms + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "CTIA",
        "T-Mobile",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite any \"only way\" wording so STOP is offered as the easiest route rather than the sole one, and name at least one non-SMS channel — the support email or phone number you already publish. Done when no sentence in the flow, the terms or the policy tells a consumer that a revocation sent another way will not be honoured.",
      "example": "Reply STOP to any message to unsubscribe. You can also opt out by emailing support@acmecoffee.com or calling 415-555-0134, and we honour requests received any of these ways.",
      "pitfalls": [
        "Adding the extra channels to the terms while the HELP reply still says STOP is the only method leaves the exclusive sentence in the one place a consumer actually reads."
      ],
      "notes": "Registration-side twin. Whether a revocation sent by email is honoured is runtime conduct we cannot observe; whether the brand has told consumers it will not be is in the submitted text, and it is the half a reviewer reads too.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-039",
      "slug": "ops-039",
      "title": "A revocation on any channel must reach the SMS suppression list",
      "statement": "Revocations captured by an IVR key-press, a live agent, a designated website or number, an email or a voicemail must all be written into the same suppression list as a texted STOP.",
      "rationale": "The consumer does not know your systems are separate. They tell the agent on the phone, or reply to the marketing email, and consider it done — and the FCC agrees with them: a revocation received at any address or number the brand holds out is presumed effective. What actually happens is that the CRM records a preference nobody syncs, the shared inbox is read by a team with no access to the messaging platform, and the texts continue for weeks.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression list ingestion across channels",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give every intake channel one write path into the suppression list — an API call, a shared field the messaging platform reads, or a queue somebody works daily — and record the channel on each record. Done when a revocation left on voicemail suppresses the number without anyone re-keying it.",
      "example": "Agent marks \"SMS opt-out\" in the CRM → nightly job (or webhook) writes the number to the messaging platform suppression list with channel=phone.",
      "pitfalls": [
        "A one-way sync from the messaging platform into the CRM is the common build and it is backwards — the revocations that need moving are the ones that arrive outside the messaging platform."
      ],
      "notes": "Absorbs OPS-040 and OPS-041, which name the designated-channel and email or voicemail cases of the same routing duty. Nothing here is visible at registration. The user has to list every place a customer could plausibly say stop — agents, shop staff, the support inbox, social messages — and show that each one reaches the list. OPS-038 is the registration-side twin: the published instructions must not name one exclusive method.",
      "catalogIds": [
        "OPS-040",
        "OPS-041"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can you name every place a customer could say stop — agents, shop staff, the support inbox, social messages, voicemail — and show that each one reaches the suppression list?",
        "howToCheck": [
          "List the intake channels, then trace one revocation through each to the list.",
          "Check the direction of any existing CRM sync: the common build is one-way out of the messaging platform, which is backwards.",
          "Test the hardest one — leave a revocation on voicemail and see whether the number is suppressed without anyone re-keying it."
        ],
        "failureLooksLike": "The CRM records a preference nobody syncs, the shared inbox is read by a team with no access to the messaging platform, and the texts continue for weeks. The consumer considered it done, and so does the FCC."
      }
    },
    {
      "id": "OPS-042",
      "slug": "ops-042",
      "title": "Somebody has to own revocations that arrive in conversation",
      "statement": "A documented process must exist for capturing revocations conveyed to customer-service agents, in-store staff or social messages.",
      "rationale": "The channels with no technical integration are the ones that fail quietly, because the revocation reaches a person who is not thinking about the messaging programme and has nowhere obvious to put it. A written procedure is what turns that from individual helpfulness into something the brand can show it does — and showing it is the point, since these are precisely the revocations a complainant will say they made.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "written revocation-intake procedure",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write down, in the place staff actually look, what to do when somebody asks to stop receiving texts: which field to set or which address to forward to, and by when. Train it once and keep the training record. Done when a new starter can find the answer without asking.",
      "example": "Support runbook: \"If a customer asks to stop texts, set SMS Opt-Out on their account before ending the contact. Do not promise a timescale — it takes effect the same day.\"",
      "notes": "A procedure inside the brand, which we never see. What the user has to do is write it and keep evidence that staff were trained on it; OPS-107 covers the training record for the internal do-not-call list, which is the same filing cabinet.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could a new starter find out what to do when a customer asks them to stop the texts, without asking anyone?",
        "howToCheck": [
          "Write it down where staff actually look: which field to set or which address to forward to, and by when.",
          "Train it once and keep the training record — the same filing cabinet as the internal do-not-call training record.",
          "Test the question on someone who joined recently."
        ],
        "failureLooksLike": "The revocation reaches a person who is not thinking about the messaging programme and has nowhere obvious to put it. These are precisely the revocations a complainant will say they made."
      }
    },
    {
      "id": "OPS-043",
      "slug": "ops-043",
      "title": "A revocation crosses media",
      "statement": "A revocation sent by text also stops robocalls to that number, and a revocation given on a call also stops texts.",
      "rationale": "The consumer revoked consent to be contacted by an automated system, and the FCC does not slice that by medium. Brands slice it anyway because the dialler and the messaging platform are different products with different suppression lists, so a customer who told the agent to stop calling then starts receiving texts — which reads as retaliation and is one of the most reliably complained-about patterns there is.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression list scope across voice and SMS",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Hold one suppression list across voice and messaging, or synchronise the two in both directions on the same day. Done when a STOP text removes the number from the dialler as well as from the campaign.",
      "example": "One do-not-contact table, consumed by both the dialler and the messaging platform, keyed on the number.",
      "notes": "Two systems, neither visible from a registration. What the user has to check is the direction of any existing sync — most brands that have one built it to feed the dialler and not to be fed by it.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a STOP text remove the number from the dialler as well as from the campaign — and does a revocation given on a call stop the texts?",
        "howToCheck": [
          "Check whether one suppression list covers voice and messaging, or whether two are synchronised in both directions on the same day.",
          "Most brands that have a sync built it to feed the dialler and not to be fed by it. Check the direction."
        ],
        "failureLooksLike": "A customer tells the agent to stop calling and then starts receiving texts. It reads as retaliation and is one of the most reliably complained-about patterns there is."
      }
    },
    {
      "id": "OPS-044",
      "slug": "ops-044",
      "title": "Suppression is brand-wide, not per number",
      "statement": "An opted-out consumer must be suppressed across every sending number and every campaign belonging to the brand, not only the number that received the STOP.",
      "rationale": "Consumers experience a brand, not a sending number. Suppressing only the number that received the STOP means the next campaign — or the next number in the pool — reaches them anyway, and from their point of view the brand ignored them and changed number to do it. That is also exactly what filter-evasion looks like from the carrier's side, so the accidental version is punished like the deliberate one.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression list scope across the brand",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Scope the suppression list to the brand and check it before every send on every campaign and number. Done when a STOP received on one campaign prevents a send on a different one to the same number.",
      "example": "Suppression keyed on (brand_id, msisdn) rather than on (campaign_id, sending_number, msisdn).",
      "pitfalls": [
        "A number pool makes this worse rather than better: the next send picks a different originator, so a per-number list looks like it is working right up until the consumer complains."
      ],
      "notes": "A property of the platform's data model. What the user has to establish is how their platform scopes suppression, because per-campaign is a common default and nothing about it is visible until somebody compares two campaigns' recipient lists.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a STOP received on one campaign prevent a send on a different campaign, or a different number, to the same person?",
        "howToCheck": [
          "Ask your platform how the suppression list is scoped. Per campaign is a common default.",
          "Test across two campaigns on the brand, and across a number pool if you use one.",
          "Compare two campaigns' recipient lists — nothing else makes this visible."
        ],
        "failureLooksLike": "The next campaign, or the next number in the pool, reaches them anyway. From the consumer's side the brand ignored them and changed number to do it, which is also what filter evasion looks like from the carrier's side."
      }
    },
    {
      "id": "OPS-045",
      "slug": "ops-045",
      "title": "Sending stops on receipt, including on a political programme",
      "statement": "No message may be sent to a number after a valid opt-out is received, whatever the campaign type.",
      "rationale": "This is the obligation every other opt-out rule exists to make achievable, and the way it is breached is almost never a decision: a send was already queued, a scheduled blast was built from a list exported yesterday, or the suppression check happens at list-build time rather than at send time. Political programmes get their own line in the carrier rules because operators assume the exemptions that cover consent also cover revocation. They do not.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "send-time suppression check",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "FCC",
        "CTIA",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the suppression list at send time, not at list-build time, and re-check anything that has been queued for more than a few minutes. Purge suppressed numbers from scheduled sends when the schedule fires rather than when it is created.",
      "pitfalls": [
        "A CSV exported for a blast is a snapshot of consent at export time; every opt-out between export and send is invisible to it unless the platform re-checks."
      ],
      "notes": "Absorbs OPS-069 and MSG-268, which restate it for political programmes where the carriers call it out specifically. Undecidable before traffic. What the user has to verify is where in their send pipeline the suppression check sits — build time is the common answer and it is the wrong one.",
      "catalogIds": [
        "MSG-268",
        "OPS-069"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Where in your send pipeline does the suppression check run — at list-build time, or at send time?",
        "howToCheck": [
          "Trace one scheduled send from list construction to dispatch and find the check. Build time is the common answer and it is the wrong one.",
          "Confirm anything queued for more than a few minutes is re-checked before it goes.",
          "Political programmes are not exempt: the exemptions that cover consent do not cover revocation."
        ],
        "failureLooksLike": "A blast built from yesterday's export goes out this morning to someone who opted out overnight. Nobody decided to send it, and the recipient cannot tell the difference."
      }
    },
    {
      "id": "OPS-046",
      "slug": "ops-046",
      "title": "The honouring clock starts when the revocation arrives",
      "statement": "The time taken to honour a revocation is measured from receipt of the revocation, not from the confirmation, and must never exceed ten business days.",
      "rationale": "Ten business days is the outer federal limit, and it is not a target — anything a platform can do instantly it must do instantly. The anchor matters because the obvious instrumentation measures from the confirmation being sent, which hides any delay in recognising the revocation at all: a message that sat unread in an inbox for a week shows as zero elapsed time on that clock.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression latency, measured from receipt",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Timestamp the revocation when it arrives, not when it is processed, and report the gap between that and suppression. Done when your own reporting would show a revocation that waited three days in a queue.",
      "example": "Opt-out record: received_at 2026-03-04T09:12Z, suppressed_at 2026-03-04T09:12Z, channel=SMS.",
      "notes": "A measurement across two runtime events. What the user has to check is which timestamp their platform records — many record only the suppression, which makes the obligation unmeasurable in their own data. OPS-056 covers the record this depends on.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Would your own reporting show a revocation that sat unread in a queue for three days?",
        "howToCheck": [
          "Check which timestamp the platform records — many record only the suppression, which makes the obligation unmeasurable in your own data.",
          "Timestamp the revocation on arrival, and report the gap between that and suppression.",
          "Ten business days is the outer federal limit, not a target."
        ],
        "failureLooksLike": "The instrumentation measures from the confirmation being sent, so a message that waited a week in an inbox shows as zero elapsed time."
      }
    },
    {
      "id": "OPS-047",
      "slug": "ops-047",
      "title": "A state may give you less time than the federal limit",
      "statement": "Where the recipient's state sets a shorter cure window for ceasing text solicitations, that window applies instead of the federal maximum.",
      "rationale": "Florida requires solicitations to cease within fifteen days of a stop reply, and its statute carries a private right of action — so the state window is not a compliance detail, it is the one with a plaintiff attached. A national programme sized to the federal ten business days is inside the law almost everywhere and outside it for a subset of recipients it cannot identify without a state lookup.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression latency against the recipient state",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (FL)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Suppress immediately as the standard behaviour, which clears every state window at once, and reserve any delay for cases where a system genuinely cannot act in real time. Done when the slowest suppression path is measured in minutes rather than days.",
      "notes": "Needs the recipient's state, which is not the area code and which we do not hold. What the user has to do is either suppress immediately everywhere — much the simpler answer — or hold a per-state window table and the location data to use it. OPS-100 is the umbrella that table belongs to.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is your slowest suppression path measured in minutes rather than days?",
        "howToCheck": [
          "Measure the slowest route — usually a manual or batch channel, not the keyword handler.",
          "Suppress immediately everywhere: that clears every state window at once and is much the simpler answer.",
          "The alternative is a per-state window table plus the location data to use it, and the area code is not the recipient's state."
        ],
        "failureLooksLike": "A national programme sized to the federal ten business days is inside the law almost everywhere and outside it in Florida, whose fifteen-day statute carries a private right of action."
      }
    },
    {
      "id": "OPS-050",
      "slug": "ops-050",
      "title": "The opt-out confirmation is worth sending inside five minutes",
      "statement": "The single permitted opt-out confirmation should reach the consumer within five minutes of the revocation.",
      "rationale": "A prompt confirmation is what buys the presumption that the revocation was honoured, and it is also the only signal the consumer gets that anything happened. Sent late it does the opposite work: a confirmation arriving the next day reads as another unwanted message from a programme they already left, and it is a common trigger for the complaint the confirmation was meant to prevent.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "opt-out confirmation latency",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send the confirmation from the same handler that performs the suppression, synchronously, rather than from a batch job. Done when the confirmation arrives while the consumer still has the conversation open.",
      "example": "STOP received 09:12:03 → suppressed 09:12:03 → confirmation delivered 09:12:05.",
      "notes": "A latency only measurable against real receipts. What the user has to confirm is that the confirmation is not queued behind an unrelated batch — the common cause of a slow one is that it shares a sending queue with marketing.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does the opt-out confirmation arrive while the consumer still has the conversation open?",
        "howToCheck": [
          "Send STOP to your own programme and time the confirmation.",
          "Check it is sent synchronously by the same handler that performs the suppression, not by a batch job sharing a queue with marketing."
        ],
        "failureLooksLike": "A confirmation arriving the next day reads as another unwanted message from a programme they already left — and triggers the complaint it was meant to prevent."
      }
    },
    {
      "id": "OPS-051",
      "slug": "ops-051",
      "title": "Only ask which messages to stop when there is more than one kind",
      "statement": "A scope-clarification question may appear in the opt-out confirmation only where the consumer consented to several distinct message categories.",
      "rationale": "Asking \"which messages would you like to stop?\" of somebody who receives one kind reads as an obstacle, and the FCC treats a revocation as complete unless the consumer chose to narrow it — so the question buys nothing and delays the suppression. Where a consumer genuinely consented to two or three categories it is legitimate and useful, which is why the rule is about the mismatch rather than about the question.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.optout_message + campaign.sub_use_cases",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the campaign sends one category of message, make the opt-out confirmation unconditional and final. Keep a scope question only where the consent covered separately-described categories, and treat no reply to it as a full revocation. Done when the confirmation matches the number of things the consumer agreed to.",
      "example": "Single-category campaign: \"Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.\"",
      "pitfalls": [
        "A scope question that keeps sending until the consumer answers it inverts the rule — silence is revocation of everything, not permission to continue."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-052",
      "slug": "ops-052",
      "title": "Silence after a scope question revokes everything",
      "statement": "Where a scope-clarification message is sent and the consumer does not reply, the revocation must be treated as covering all robocalls and robotexts from the sender.",
      "rationale": "Asking which messages to stop is permitted, but it cannot be a way of keeping somebody subscribed by default. A consumer who said stop and then ignored a follow-up question has not narrowed anything — and a system that treats no reply as \"carry on with the rest\" has converted a revocation into a menu the consumer never answered.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "scope-clarification timeout handling",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Suppress everything at the moment the revocation arrives, and treat any reply to the scope question as a request to re-subscribe to a subset. Done when no reply leaves the consumer receiving nothing.",
      "example": "STOP → suppress all → \"You have been unsubscribed from all Acme Coffee texts. Reply ORDERS if you would still like order updates.\"",
      "pitfalls": [
        "Suppressing only after a timeout expires leaves a window in which the programme is still sending to somebody who revoked — suppress first, ask second."
      ],
      "notes": "Only reachable once a scope question has been sent, which is a design choice the registration does not record. OPS-051 is the registration-side twin: it checks that the declared confirmation only asks a scope question where the consent covered several categories.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "If a consumer sends STOP and never replies to your scope question, do they receive nothing?",
        "howToCheck": [
          "Confirm the handler suppresses everything the moment the revocation arrives, before the question is sent.",
          "Treat any reply to the scope question as a request to re-subscribe to a subset, not as permission to continue.",
          "Test it: send STOP, ignore the follow-up, and check nothing else arrives."
        ],
        "failureLooksLike": "Suppressing only after a timeout leaves a window in which the programme is still sending to someone who revoked, and a system that reads no reply as \"carry on with the rest\" has converted a revocation into a menu nobody answered."
      }
    },
    {
      "id": "OPS-053",
      "slug": "ops-053",
      "title": "A STOP answering a scope question must not be answered with another",
      "statement": "A STOP sent in reply to a scope-clarification message must not trigger a second clarification message.",
      "rationale": "The loop is the failure: a consumer replies STOP, gets asked which messages, replies STOP again, and gets asked again — each round a message to somebody who has now revoked twice. It happens because the clarification is wired to the keyword rather than to the state, and it is the version of this family that most reliably produces a screenshot in a complaint.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "scope-clarification loop guard",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Track whether a clarification has already been sent to this number and never send a second one; a repeat STOP gets the plain confirmation. Done when replying STOP twice produces one clarification and one confirmation, in that order, and nothing else.",
      "example": "STOP → clarification. STOP again → \"You have been unsubscribed and will receive no further messages.\"",
      "notes": "A state machine inside the handler. What the user has to test is the double-STOP path specifically, because the single-STOP path looks correct in every demonstration.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does replying STOP twice produce one clarification and one confirmation, in that order, and nothing else?",
        "howToCheck": [
          "Test the double-STOP path specifically. The single-STOP path looks correct in every demonstration.",
          "Confirm the clarification is wired to the consumer's state rather than to the keyword, so a second one can never be sent."
        ],
        "failureLooksLike": "STOP, asked which messages, STOP again, asked again. Each round is a message to somebody who has now revoked twice, and it is the version of this family that most reliably produces a screenshot in a complaint."
      }
    },
    {
      "id": "OPS-054",
      "slug": "ops-054",
      "title": "Revoking against an exempt message stops everything else too",
      "statement": "Where a consumer revokes in direct response to an exempted informational message, all further non-emergency robocalls and robotexts must stop.",
      "rationale": "Some informational messages need no prior consent, and operators reason from that to \"there is no consent here to revoke\". The FCC settled it the other way: replying stop to an exempt message is a revocation of everything non-emergency. The practical failure is a delivery-notification programme that keeps notifying after a stop, on the grounds that notifications were never opt-in.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression scope after a revocation",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Treat a revocation as brand-wide and category-blind unless the consumer narrowed it themselves. Done when a stop sent to a shipping notification also stops the marketing programme on the same number.",
      "notes": "Depends on which message a revocation answered, which only exists in the traffic. What the user has to check is whether their exempt or transactional flows consult the suppression list at all — many are built as system notifications that bypass it.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do your transactional and exempt flows — shipping notices, alerts, system messages — consult the suppression list before sending?",
        "howToCheck": [
          "Trace one exempt flow end to end and find the suppression check. Many are built as system notifications that bypass it entirely.",
          "Test it: reply stop to a shipping notification and confirm the marketing programme on that number also stops."
        ],
        "failureLooksLike": "Operators reason that informational messages needed no consent, so there is none to revoke. The FCC settled it the other way, and a delivery-notification programme that keeps notifying after a stop is the practical failure."
      }
    },
    {
      "id": "OPS-055",
      "slug": "ops-055",
      "title": "Revoke-all across categories has a deadline",
      "statement": "A suppression architecture that cannot revoke across every message category for a sender must be flagged before the federal revoke-all rule takes effect on 31 January 2027.",
      "rationale": "The rule is scheduled rather than hypothetical, and the work it implies is architectural: a brand whose consent and suppression are modelled per programme cannot become category-wide with a configuration change. Naming it now is what makes it a planned migration rather than a discovery made in the month it applies.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression list data model",
      "severity": "LOW",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check now whether one revocation can suppress every category a sender operates, and if it cannot, schedule the change against the January 2027 date. Done when a single revocation demonstrably clears every programme the brand runs.",
      "effectiveFrom": "2027-01-31",
      "notes": "A future obligation about a system we cannot inspect, carried at LOW until it takes effect and BLOCKING after. What the user has to do is answer one question about their own platform — is suppression keyed per sender or per programme — and start the migration if the answer is the second.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is suppression on your platform keyed per sender or per programme — and if per programme, is the migration scheduled against 31 January 2027?",
        "howToCheck": [
          "Answer the one architectural question: can a single revocation clear every category this sender operates?",
          "If it cannot, schedule the change now. A per-programme consent model does not become category-wide with a configuration change."
        ],
        "failureLooksLike": "The rule is scheduled rather than hypothetical, and it is discovered in the month it applies — when the work it implies is architectural rather than a setting."
      }
    },
    {
      "id": "OPS-056",
      "slug": "ops-056",
      "title": "Opt-out records must keep the words the consumer actually sent",
      "statement": "Every opt-out transaction must be recorded with the raw inbound text, the channel it arrived on, and the normalisation that was applied to it.",
      "rationale": "The hard opt-out cases are never \"STOP\". They are \"stop texting me\", a reply on the wrong keyword, a revocation left on a voicemail — and whether each was honoured turns on what the handler did with the exact words. A record keeping only \"opted out: true\" cannot show that a borderline message was read correctly, and cannot show that a missed one was missed, which is the same as having no answer when a regulator asks how revocations are recognised.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Store three things per revocation: the inbound text verbatim, the channel it arrived on (SMS, call, email, agent), and the keyword or intent it was matched to. Done when you can list last month's opt-outs and read what each consumer actually wrote.",
      "example": "Opt-out records store: received_at, channel=SMS, raw_text=\"please stop texting me\", matched=STOP (natural-language), phone number.",
      "pitfalls": [
        "Recording the normalised keyword only (\"STOP\") loses exactly the evidence the rule exists for — the raw text is what shows a variant was understood."
      ],
      "notes": "Absorbs OPS-220, which names the channel field this record must carry. Registration-side twin: the handler behaviour itself is not observable before traffic, but whether the programme has decided to keep the evidence is.",
      "catalogIds": [
        "OPS-220"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-057",
      "slug": "ops-057",
      "title": "Log the sends you did not make",
      "statement": "Every send attempt suppressed because the number was opted out must be logged.",
      "rationale": "The suppression list proves what you intended; the suppressed-send log proves it worked. Without it there is no evidence that the check ran at all — an empty complaint file and a suppression list with a million entries are consistent with a suppression check that has been silently failing since a deploy, and an audit asks for exactly this.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppressed-send log",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Log the number, the campaign and the timestamp each time a send is dropped for suppression, and retain it for the same period as the consent records. Done when you can show how many sends the list stopped last month.",
      "example": "suppressed_send: msisdn +14155550134, campaign acme-marketing, at 2026-03-04T14:22Z, reason=opted_out.",
      "notes": "A log inside the sending platform. What the user has to check is whether their platform exposes suppression events at all — several drop them silently, and where that is true the evidence has to come from a pre-send check they run themselves.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can you say how many sends your suppression list stopped last month?",
        "howToCheck": [
          "Check whether your platform exposes suppression events at all — several drop them silently, and where that is true the evidence has to come from a pre-send check you run yourself.",
          "Log the number, the campaign and the timestamp on every dropped send.",
          "Retain it for the same period as the consent records."
        ],
        "failureLooksLike": "An empty complaint file and a suppression list with a million entries are equally consistent with a check that has been silently failing since a deploy. Without the log there is no evidence it ever ran."
      }
    },
    {
      "id": "OPS-058",
      "slug": "ops-058",
      "title": "Re-read the inbound log for opt-outs the handler missed",
      "statement": "Inbound message logs must be reviewed on a schedule for revocations the keyword handler did not catch, and those subscriptions terminated.",
      "rationale": "No intent classifier catches everything, so the question is not whether some revocations are missed but whether anybody ever finds out. A scheduled sweep converts a silent miss into a caught one — and finding three a month is a working programme, while finding none usually means nobody is looking rather than that the handler is perfect.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "inbound log review",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Schedule a review of inbound messages that produced no keyword match, suppress anything that reads as a revocation, and record when the review ran. Done when the review has a date, an owner and a finding count each time.",
      "example": "Weekly: export unmatched inbound messages, review, suppress, record \"reviewed 2026-03-06, 4 revocations found\".",
      "notes": "A recurring job over data we never see. What the user has to do is own the schedule and keep its output — the review record is what shows a missed revocation was an exception rather than the norm.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does the review of unmatched inbound messages have a date, an owner and a finding count each time it runs?",
        "howToCheck": [
          "Schedule the sweep over inbound messages that produced no keyword match.",
          "Suppress anything that reads as a revocation, and record when the review ran and what it found.",
          "Finding three a month is a working programme. Finding none usually means nobody is looking."
        ],
        "failureLooksLike": "No classifier catches everything, so the question is whether anybody ever finds out. Without the review record, a missed revocation looks like the norm rather than an exception."
      }
    },
    {
      "id": "OPS-061",
      "slug": "ops-061",
      "title": "A later form fill does not undo an earlier opt-out",
      "statement": "A consumer who opted out must not be re-subscribed on the strength of a subsequent form submission without a fresh, separately documented opt-in.",
      "rationale": "Somebody who unsubscribed in March and buys something in June has given you their number for the order, not permission to restart the programme they left. Systems do it automatically because the checkout writes the number to the marketing list the same way it always did, and the result is a consumer receiving messages from a programme they explicitly quit — the pattern that produces the angriest complaints.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "consent record against opt-out record",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the suppression list before writing any new consent, and require an explicit, separately recorded opt-in — not a pre-ticked box on a checkout — before removing a number from it. Done when a previously-opted-out number stays suppressed through an ordinary purchase.",
      "example": "Checkout writes consent → suppression list hit → consent stored as pending, no messages sent, and the customer is shown the opt-in box unticked.",
      "pitfalls": [
        "Treating the new consent as newer and therefore controlling is the intuitive data model and the wrong one; an opt-out is a standing instruction, not a timestamped preference."
      ],
      "notes": "A comparison between two records that only exist after launch. What the user has to check is the order of operations at their own signup surfaces — most write consent first and reconcile later, if at all.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a previously opted-out number stay suppressed through an ordinary purchase?",
        "howToCheck": [
          "Check the order of operations at every signup surface: most write consent first and reconcile later, if at all.",
          "Require an explicit, separately recorded opt-in — not a pre-ticked checkout box — before removing anyone from the suppression list.",
          "Test it with a number that opted out months ago."
        ],
        "failureLooksLike": "Someone who unsubscribed in March buys something in June and the checkout writes their number back to the marketing list. Treating the newer record as controlling is the intuitive data model and the wrong one."
      }
    },
    {
      "id": "OPS-062",
      "slug": "ops-062",
      "title": "An opted-out consumer must be able to come back",
      "statement": "The programme must leave a published route back for a consumer who opted out and later wants the messages again.",
      "rationale": "Opting out is not meant to be permanent exile, and a consumer who wants their order alerts back should not have to phone somebody. The route matters more than it sounds: where none exists, the consumer either goes without or is re-added by staff without a fresh opt-in, which converts a support request into an unconsented enrolment. Programmes lose the route by accident, when the signup page starts checking the suppression list and silently drops the resubmission.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + campaign.optin_keywords",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "Bird",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep the published call-to-action open to previously-opted-out numbers, and support START (or UNSTOP) where the campaign accepts keyword opt-in, so the consumer can rejoin from the handset. Say which route exists in the message flow. Done when a number that texted STOP can rejoin without an agent touching the record.",
      "example": "A consumer who opted out can rejoin at any time by texting START, or by opting in again at acmecoffee.com/signup; each rejoin is recorded as a new consent.",
      "notes": "Registration-side twin: whether START is honoured is runtime behaviour. What is decidable now is whether a route back was designed at all, and whether the flow says re-enrolment is treated as fresh consent rather than as reactivating an old record.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-063",
      "slug": "ops-063",
      "title": "Opt-out records must not be shared onward",
      "statement": "The published policy must not permit sharing or forwarding opt-out records to unaffiliated parties without the consumer's prior express permission.",
      "rationale": "A suppression list is a list of people who asked a specific sender to stop, and passing it on turns a refusal into a marketing asset — the consumer discovers that saying no put them on a new list. Policies allow it by accident: a general \"we may share data with partners\" clause written for analytics also covers the suppression file, because nobody carved it out.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "privacy policy + SMS terms",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "FCC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a sentence to the SMS section stating that opt-out and suppression records are used only to stop messages and are never sold, rented or shared. Done when a general sharing clause elsewhere in the policy no longer reads as covering them.",
      "example": "Opt-out requests are recorded and used solely to suppress further messages. We do not sell, rent or share opt-out records with any third party.",
      "pitfalls": [
        "A carve-out in the SMS section does not help if a broader sharing clause earlier in the policy is unqualified — reviewers read the permissive clause as controlling."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-065",
      "slug": "ops-065",
      "title": "A carrier opt-out is authoritative even if your list has no record",
      "statement": "A delivery rejection indicating the carrier holds an opt-out for the number must be treated as a suppression event in its own right.",
      "rationale": "Carriers maintain their own opt-out state, and a consumer who texted STOP to a shared originator or used a handset-level block can be opted out there while your list shows them active. Retrying that number burns delivery reputation and, more importantly, means you are trying to message somebody who has revoked — your own list not knowing about it is not a defence.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "delivery error stream into the suppression list",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Bandwidth",
        "all MNOs"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write back to the suppression list on every opt-out-class delivery error rather than counting it as a failed send. Done when a carrier-held opt-out appears in your own suppression list within a day of the first rejection.",
      "example": "Delivery error 21610 (opted out) → suppress the number locally, channel=carrier, and stop retrying.",
      "notes": "Reaches you as a delivery code after launch. What the user has to establish is whether their platform surfaces opt-out-class errors distinctly, and whether anything consumes them — the common answer is that they land in a failure metric nobody reads.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does anything in your systems consume opt-out-class delivery errors and write them to the suppression list?",
        "howToCheck": [
          "Ask whether the platform surfaces opt-out-class errors distinctly from other delivery failures.",
          "Trace where they go. The common answer is a failure metric nobody reads.",
          "Done when a carrier-held opt-out appears in your own list within a day of the first rejection."
        ],
        "failureLooksLike": "A consumer opted out at the carrier or blocked the number on the handset, your list shows them active, and you retry — messaging somebody who has revoked. Your list not knowing is not a defence."
      }
    },
    {
      "id": "OPS-066",
      "slug": "ops-066",
      "title": "Watch the opt-out rate, and act at both thresholds",
      "statement": "The per-campaign opt-out rate must be monitored, audited above roughly 0.5% on a send, and the campaign suspended with a root-cause analysis above 4% in 24 hours.",
      "rationale": "The opt-out rate is the earliest honest signal that a list is not what it was thought to be, and it moves before complaints do. The two thresholds mean different things: the lower one says audit where these numbers came from, the higher one says stop, because a rate that high is usually a purchased list or a consent scope nobody checked, and carriers act on it whether or not you do.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-campaign opt-out rate",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Report the opt-out rate per send and per rolling day, alarm at 0.5% and 4%, and write down what each alarm triggers — a consent audit at the lower, an immediate pause and root-cause analysis at the higher. Done when the numbers have owners rather than dashboards.",
      "example": "Send of 40,000 → 260 opt-outs (0.65%) → consent audit on the segment before the next send.",
      "notes": "Absorbs OPS-067, the higher threshold on the same monitor. Needs live traffic. What the user has to do is decide now who is paged at 4% and what authority they have to stop a send, because the window in which acting helps is hours.",
      "catalogIds": [
        "OPS-067"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Who is paged when the opt-out rate hits 4% in 24 hours, and do they have the authority to stop a send?",
        "howToCheck": [
          "Report the rate per send and per rolling day, and alarm at 0.5% and 4%.",
          "Write down what each alarm triggers: a consent audit at the lower, an immediate pause and root-cause analysis at the higher.",
          "Name the person now — the window in which acting helps is hours."
        ],
        "failureLooksLike": "A rate that high is usually a purchased list or a consent scope nobody checked, and carriers act on it whether or not you do. Dashboards without owners are how the hours pass."
      }
    },
    {
      "id": "OPS-068",
      "slug": "ops-068",
      "title": "STOP and HELP volumes are a standing report",
      "statement": "STOP and HELP response volumes must be monitored per campaign as an ongoing obligation.",
      "rationale": "Aggregate rates hide the campaign that is going wrong. A brand running six programmes sees a healthy overall opt-out rate while one of them is generating almost all of it, and the per-campaign view is what makes that visible early enough to fix. It is also what the connectivity partner is expected to hold, so being unable to produce it is itself a finding.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-campaign keyword response metrics",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Break STOP and HELP counts out per campaign rather than per account, and keep the series long enough to see a trend. Done when you can answer \"which campaign is driving this\" without an export.",
      "example": "Monthly: acme-marketing 0.4% STOP / 0.1% HELP; acme-orders 0.05% STOP / 0.3% HELP.",
      "notes": "Metrics from live traffic. What the user has to confirm is that their platform attributes inbound keywords to a campaign at all — where several campaigns share a number, many do not, and the attribution has to be rebuilt from the outbound history.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can you answer \"which campaign is driving this\" without an export?",
        "howToCheck": [
          "Break STOP and HELP counts out per campaign rather than per account, and keep the series long enough to see a trend.",
          "Confirm the platform attributes inbound keywords to a campaign at all — where several campaigns share a number, many do not."
        ],
        "failureLooksLike": "A brand running six programmes sees a healthy overall opt-out rate while one programme generates almost all of it. Being unable to produce the per-campaign view is itself a finding."
      }
    },
    {
      "id": "OPS-070",
      "slug": "ops-070",
      "title": "A referral opt-out covers everything sent through the application",
      "statement": "In a viral or referral programme, an opt-out must be honoured across every message sent through that application, programme or software.",
      "rationale": "In a referral programme the messages a consumer receives are triggered by other consumers, so per-sender suppression leaves them able to be re-invited indefinitely by anyone who has their number. From the recipient's side it is the same application messaging them again after they said stop, and no amount of \"a different friend sent that one\" survives a complaint.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "suppression scope across a viral application",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Hold suppression at the application level and check it before any referral send, whoever triggered it. Done when a number that opted out cannot be re-invited by a different referrer.",
      "notes": "Applies only to referral or viral messaging, which the registration does not record. What the user has to check is whether their suppression is scoped to the sender or to the application — per-sender is the natural build and it does not satisfy this. OPS-385 covers the registration-side prohibition on paying for forwards.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can a number that opted out be re-invited by a different referrer?",
        "howToCheck": [
          "Check whether suppression is scoped to the sender or to the application. Per-sender is the natural build and it does not satisfy this.",
          "Test it: opt a number out, then have a second referrer invite the same number."
        ],
        "failureLooksLike": "From the recipient's side it is the same application messaging them again after they said stop, and \"a different friend sent that one\" does not survive a complaint."
      }
    },
    {
      "id": "OPS-071",
      "slug": "ops-071",
      "title": "Group messaging needs a member opt-out and anti-abuse controls",
      "statement": "A group-messaging programme must let any member leave at any time, carry anti-abuse controls proportionate to the distribution size, and prevent recursive group messaging.",
      "rationale": "In a group thread the sender is not the only person who can reach the recipient, so the ordinary opt-out is not enough — a member who leaves can be added back by anyone else in the group, and a recursive group can amplify one message into thousands. The three controls exist together because each one alone is defeated by the other two failure modes.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe how a member leaves and stays left, what limits the size and rate of a distribution, and what stops a group creating another group. Done when the flow answers all three without the reader inferring any of them.",
      "example": "Any participant can reply STOP to leave a thread and cannot be re-added without opting in again; threads are capped at 25 participants, and participants cannot create new threads through the service.",
      "notes": "Applies only to group or multi-recipient threading, which the registration has no attribute for. The condition is carried in the criteria — the judge PASSes immediately where the campaign describes ordinary one-to-one messaging — and this note records that the applicability model could not express it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-072",
      "slug": "ops-072",
      "title": "The programme must not evade opt-out detection",
      "statement": "Content engineered to defeat opt-out or content detection — deliberate misspellings, non-standard phrasing — is prohibited.",
      "rationale": "Filter evasion escalates a campaign from being filtered to being enforced against, because it demonstrates intent rather than error. It is also self-defeating: a consumer whose opt-out does not register complains to the carrier instead.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.sample[] + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any deliberate misspelling, character substitution, or spacing trick. Write plainly and let the content stand on its own.",
      "catalogIds": [
        "OPS-148"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-073",
      "slug": "ops-073",
      "title": "Toll-free keyword behaviour cannot be customised, so do not promise it",
      "statement": "A toll-free programme must not tell consumers that STOP, UNSTOP or START will behave in any way other than the fixed carrier behaviour.",
      "rationale": "On toll-free numbers the mandatory keywords are handled by the carrier before the message ever reaches the sender, and the behaviour cannot be changed or intercepted. Copy promising something else — \"reply STOP PROMOS to leave only marketing\", \"text UNSTOP to resume your alerts\" — describes a programme that cannot exist, so the consumer's reply does something different from what they were told and the brand looks like it ignored them.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + campaign.sample[] + auto-replies",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe only the standard behaviour: STOP stops everything on that number, START resumes it, HELP returns support information. Move any per-category preference to a link or a preference centre rather than a keyword. Done when no copy promises a keyword outcome the carrier does not implement.",
      "example": "Reply STOP to unsubscribe. To choose which Acme Coffee messages you get, visit acmecoffee.com/preferences.",
      "notes": "Applies to the toll-free path only; on 10DLC long codes the keyword handler is the sender's own and custom keywords are legitimate. The applicability model has no dimension for the submission path, so the condition is carried in the criteria — the judge is told to PASS immediately unless the submission is toll-free.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-074",
      "slug": "ops-074",
      "title": "Toll-free opt-out language is appended for you, and billed",
      "statement": "On toll-free traffic, expect the carrier to append opt-out language to outbound messages — it is billable, cannot be removed and can only be shortened.",
      "rationale": "The append lands on messages that were already at the segment boundary, so a body sized to 160 characters becomes two segments and the bill doubles with no change on your side. Teams discover it as an unexplained cost increase and go looking for a fault in their own templates, because nothing in the sending API mentions it.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound toll-free message bodies",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Size toll-free message bodies leaving room for the appended text, and include your own short opt-out line so the appended version is shortened rather than added in full. Done when a representative message still fits one segment after the append.",
      "example": "Body written to ~120 characters on toll-free, with \"Reply STOP to end\" already present.",
      "notes": "Applies to the toll-free path and happens in the network. What the user has to do is measure a real delivered message rather than the one they submitted — the append is only visible on the handset and on the invoice.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a representative message still fit one segment after the carrier appends its opt-out language?",
        "howToCheck": [
          "Measure a real delivered message on a handset, not the body you submitted — the append is only visible there and on the invoice.",
          "Include your own short opt-out line so the appended version is shortened rather than added in full.",
          "Size bodies leaving room for it."
        ],
        "failureLooksLike": "A body sized to 160 characters becomes two segments and the bill doubles with no change on your side. Nothing in the sending API mentions it, so the search for the fault starts in your own templates."
      }
    },
    {
      "id": "OPS-075",
      "slug": "ops-075",
      "title": "Opt-out instructions must recur, not just appear at opt-in",
      "statement": "A recurring programme must carry opt-out instructions at opt-in and at regular intervals thereafter — at least monthly.",
      "rationale": "Subscribers forget what they joined and how to leave, and the reply that stops the programme is only obvious while the sign-up is recent. A programme that stops repeating the instruction converts would-be unsubscribers into complainants, because reporting the message as spam becomes the easiest available action.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound message cadence",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Carry \"Reply STOP to opt out\" in every message where the length allows, and at minimum in one message a month. Done when no subscriber goes a calendar month without being told how to leave.",
      "example": "Acme Coffee: 20% off beans this weekend. Shop acmecoffee.com/sale. Reply STOP to opt out.",
      "notes": "A cadence across live traffic. What the user has to check is the templates that dropped the line for length — those are the ones that go months without it, and they are usually the highest-volume ones.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does any subscriber go a calendar month without being told how to leave?",
        "howToCheck": [
          "Audit the templates that dropped the opt-out line for length — those are the ones that go months without it, and they are usually the highest-volume ones.",
          "Carry \"Reply STOP to opt out\" wherever the length allows, and at minimum in one message a month."
        ],
        "failureLooksLike": "Subscribers forget what they joined and how to leave, so reporting the message as spam becomes the easiest available action."
      }
    },
    {
      "id": "OPS-076",
      "slug": "ops-076",
      "title": "Customer-care contact details must recur too",
      "statement": "Customer-care contact instructions must be promoted at opt-in and at regular intervals thereafter, at least monthly.",
      "rationale": "The opt-out is not the only thing a subscriber needs to be able to find; a consumer who does not recognise a message needs a route to a person before their only remaining option is a spam report. HELP covers it on request, but a recipient who does not know HELP exists will not send it, which is why the instruction has to appear rather than wait to be asked for.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound message cadence",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include \"Reply HELP for help\" alongside the opt-out line, and put the support address or number itself into at least one message a month. Done when a subscriber can reach a person without searching for the brand.",
      "example": "Acme Coffee: reply HELP for help or email support@acmecoffee.com. Reply STOP to opt out.",
      "notes": "Absorbs MSG-080, which states the same cadence from the message-content side. A property of live sends. What the user has to do is check the monthly template rather than the sample set — samples almost always carry both lines and live traffic often does not.",
      "catalogIds": [
        "MSG-080"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can a subscriber reach a person without searching for the brand — in at least one message a month?",
        "howToCheck": [
          "Check the monthly template rather than the sample set. Samples almost always carry both lines and live traffic often does not.",
          "Include \"Reply HELP for help\" alongside the opt-out line, and put the support address or number itself into at least one message a month."
        ],
        "failureLooksLike": "A recipient who does not recognise a message and does not know HELP exists will not send it. Their only remaining option is a spam report."
      }
    },
    {
      "id": "OPS-078",
      "slug": "ops-078",
      "title": "HELP is answered whoever asks",
      "statement": "A HELP request must always produce a response, whether or not the sender is currently subscribed and whether or not the programme is a subscription.",
      "rationale": "The people most in need of a HELP response are the ones who are not subscribers: somebody who received a message meant for a previous holder of their number, or who cannot tell what the programme is. A handler that only answers known subscribers leaves exactly those people with silence and a number they cannot identify, which converts a fixable confusion into a carrier complaint.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "HELP keyword handler",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "MMA",
        "Aerialink"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Answer HELP from the number that received it, without looking the sender up first. Done when HELP sent from a number that never subscribed still returns the brand name and a support route.",
      "example": "Acme Coffee: for help, email support@acmecoffee.com or call 415-555-0134. Reply STOP to unsubscribe.",
      "notes": "Absorbs OPS-082, the same duty stated for customer-care requests generally. Not observable before traffic. What the user has to test is the unsubscribed case: send HELP from a number that is not on the list and confirm a reply comes back.",
      "catalogIds": [
        "OPS-082"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does HELP sent from a number that never subscribed still return the brand name and a support route?",
        "howToCheck": [
          "Test the unsubscribed case: send HELP from a number that is not on the list.",
          "Confirm the handler answers from the number that received it, without looking the sender up first."
        ],
        "failureLooksLike": "The people most in need of a HELP reply are the ones who are not subscribers — someone who got a message meant for a previous holder of their number. Silence turns a fixable confusion into a carrier complaint."
      }
    },
    {
      "id": "OPS-079",
      "slug": "ops-079",
      "title": "HELP must work in every form it arrives in",
      "statement": "HELP must be case-insensitive, functional over both SMS and MMS, and functional in the programme's language without erroring on the English keyword.",
      "rationale": "HELP is the fallback when everything else has confused somebody, so it is the last handler that should be fussy about how it is spelled or which protocol carried it. Each of these variations is a separate code path in most platforms, and the ones that fail are invariably the ones nobody tested because the happy path was obvious.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "HELP keyword handler",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "MMA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Normalise the inbound body before matching, share one handler across SMS and MMS, and register the local-language help word alongside HELP rather than instead of it. Done when \"help\", \"AYUDA\" and a HELP sent as an MMS reply all return the same information.",
      "example": "Keyword table: HELP, INFO, SUPPORT, AYUDA — matched case-insensitively on either protocol.",
      "notes": "Three code paths we cannot see. What the user has to do is send the three variants to their own number after launch; OPS-081 is the registration-side twin, checking that the destinations inside the reply actually reach help.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do \"help\", \"AYUDA\" and a HELP sent as an MMS reply all return the same information?",
        "howToCheck": [
          "Send all three to your own number after launch.",
          "Confirm the local-language help word was registered alongside HELP rather than instead of it.",
          "Confirm one handler serves both SMS and MMS."
        ],
        "failureLooksLike": "Each variation is a separate code path in most platforms, and the ones that fail are invariably the ones nobody tested because the happy path was obvious."
      }
    },
    {
      "id": "OPS-081",
      "slug": "ops-081",
      "title": "The HELP reply must send people somewhere that answers",
      "statement": "Every destination named in the HELP reply — a phone number, a URL or an email address — must actually reach help.",
      "rationale": "HELP exists so a recipient who does not recognise a number can find out who is texting and how to make it stop. A reply pointing at a number that rings out, a mailbox nobody reads, or a URL that 404s satisfies a presence check and fails the consumer entirely, which is why the handbook names dead ends specifically. The usual cause is a support address that was retired after the auto-reply was written.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.help_message",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM",
        "HUMAN"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "MMA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Test every destination in the HELP reply the way a recipient would: dial the number, send a message to the address, open the link. Replace anything that does not reach a person who can help. Done when each destination has been exercised end to end since the reply was last edited.",
      "example": "Acme Coffee: for help, email support@acmecoffee.com or call 415-555-0134. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "pitfalls": [
        "A support address that auto-replies \"this mailbox is not monitored\" is a dead end with extra steps, and reviewers treat it as one."
      ],
      "notes": "Only partly settleable from here. We can see whether the destination belongs to the brand and whether the site corroborates it, and we can flag one that plainly does not — but whether a number is answered is not observable from a registration. The user has to place the call, send the email and open the link themselves, from outside their own network, and confirm each reaches a person.",
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Have you dialled the number, mailed the address and opened the link in the HELP reply yourself, from outside your own network, since it was last edited?",
        "howToCheck": [
          "Dial the number and confirm a person who can help answers.",
          "Send a message to the address and confirm a human replies.",
          "Open the link and confirm it loads a page that answers the question a confused recipient has."
        ],
        "failureLooksLike": "A support address retired after the auto-reply was written. A mailbox that auto-replies \"this mailbox is not monitored\" is a dead end with extra steps, and reviewers treat it as one."
      }
    },
    {
      "id": "OPS-083",
      "slug": "ops-083",
      "title": "Carrier deactivation files must be ingested daily",
      "statement": "The carrier deactivation feeds must be processed every day and the listed numbers removed from every opt-in list.",
      "rationale": "Around a hundred thousand US numbers are deactivated daily and most are reissued to somebody else within months. A consent record follows the number, so an unscrubbed list sends order details, appointment reminders or marketing to a stranger who never opted in — and delivery succeeds, so nothing in the sending metrics reveals it. The carriers publish the feeds specifically to make this preventable, which is why failing to consume them reads as negligence rather than bad luck.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "deactivation feed ingest job",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "CTIA",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm which party consumes the feeds — most CPaaS platforms offer it, several charge for it, and a few leave it entirely to the sender — then run the ingest daily and log each run with its timestamp. Done when a number deactivated on Monday is off your list on Tuesday.",
      "example": "Daily 06:00: pull the T-Mobile and AT&T deactivation files, suppress the listed MDNs, log run_at and record count.",
      "pitfalls": [
        "Delivery failures are not a substitute. A reassigned number delivers perfectly — to the wrong person — so bounce handling never finds these."
      ],
      "notes": "Absorbs OPS-084 and OPS-086, the same daily ingest against a second carrier feed and against a platform-mediated deactivation report. The feeds are carrier data we cannot see. What the user has to establish is whether their provider ingests them on their behalf, whether it is enabled on their account, and what the retention window on those report objects is — several platforms delete them after a few days, so a missed run cannot be replayed. OPS-085 is the approval-side twin: whether the programme describes doing this at all.",
      "catalogIds": [
        "OPS-084",
        "OPS-086"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is a number deactivated on Monday off your list on Tuesday — and who is running that ingest?",
        "howToCheck": [
          "Establish which party consumes the carrier deactivation feeds: most CPaaS platforms offer it, several charge for it, and a few leave it entirely to the sender.",
          "Confirm it is enabled on your account, runs daily, and logs each run with a timestamp.",
          "Check the retention window on the report objects — several platforms delete them after a few days, so a missed run cannot be replayed."
        ],
        "failureLooksLike": "Around a hundred thousand US numbers are deactivated daily and most are reissued within months. Delivery to a reassigned number succeeds perfectly — to a stranger — so bounce handling never finds these."
      }
    },
    {
      "id": "OPS-085",
      "slug": "ops-085",
      "title": "The programme must scrub deactivated numbers before sending",
      "statement": "The programme must describe checking carrier deactivation data before each send, and logging each ingest.",
      "rationale": "Around 100,000 US numbers change hands every day, and a consent record follows the number rather than the person. Messaging a reassigned number sends someone else's order details, or marketing, to a stranger who never opted in — the single largest source of complaints that look like consent failures and are not. The carriers publish daily deactivation files precisely so this is preventable, and a programme with no ingest described has not decided who runs it.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State which deactivation feed the programme consumes — the carrier files through your provider, or the provider's own deactivation report — how often it runs, and that listed numbers are suppressed before the next send. Done when the flow names a source and a cadence rather than an intention.",
      "example": "Carrier deactivation lists are ingested daily through our messaging provider; listed numbers are suppressed before any send, and each ingest run is logged with its timestamp.",
      "pitfalls": [
        "Relying on delivery failures to find dead numbers does not work here — a reassigned number delivers perfectly, to the wrong person."
      ],
      "notes": "Registration-side twin. The daily ingest itself (OPS-083, OPS-084, OPS-086) is a scheduled job we cannot observe; what we judge is whether the programme has one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-087",
      "slug": "ops-087",
      "title": "The reassigned-numbers database is the only route to the safe harbour",
      "statement": "The FCC Reassigned Numbers Database must be queried using the date of consent, and re-queried at least every 30 days.",
      "rationale": "The database is the one mechanism that converts \"we messaged the wrong person\" from strict liability into a defence, and the defence turns entirely on querying with the date consent was obtained — a query without that date proves nothing about whether the number had already moved on. Brands skip it because it is a paid lookup that returns nothing useful most of the time, which is exactly what an insurance policy looks like.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "reassigned-numbers query, keyed on the consent date",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Query the database with each number and the date its consent was recorded, keep the response, and re-query at least monthly for numbers still on the list. Done when you can produce, for any number you messaged, the query that said it had not been reassigned since consent.",
      "example": "Monthly: submit (msisdn, consent_date) for every active subscriber; retain the response as the safe-harbour record.",
      "notes": "A paid federal lookup we cannot make on the brand's behalf. What the user has to do is register for access, budget the per-query cost, and — the part most often missed — make sure the consent date they submit is the real one, because the safe harbour is only as good as that field. OPS-199 covers keeping the date in the first place.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "For any number you messaged, can you produce a Reassigned Numbers Database query, made with the real consent date, saying it had not been reassigned?",
        "howToCheck": [
          "Register for access to the FCC database and budget the per-query cost.",
          "Query with each number and the date its consent was recorded, and keep the response.",
          "Re-query at least every 30 days for numbers still on the list.",
          "Check the consent date you submit is the real one — the safe harbour is only as good as that field."
        ],
        "failureLooksLike": "The lookup is skipped because it is paid and returns nothing useful most of the time, which is exactly what an insurance policy looks like. Without it, messaging the wrong person is strict liability rather than a defence."
      }
    },
    {
      "id": "OPS-088",
      "slug": "ops-088",
      "title": "Some states make reassigned-number screening mandatory",
      "statement": "Where a state requires it, reassigned-number screening is an obligation rather than a safe-harbour option.",
      "rationale": "Maine converts the federal safe harbour into a duty, which changes the calculus completely: skipping the lookup is no longer accepting a risk, it is a violation on its own terms. National programmes miss it because the federal framing — screen if you want the defence — is the one everybody read.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "reassigned-number screening policy",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (ME)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Screen every number rather than maintaining a state-conditional policy — the lookup is the same one the safe harbour needs, so doing it universally satisfies both. Done when screening is unconditional rather than triggered by a state field you may not hold.",
      "notes": "Needs the recipient state, which we do not hold and which is not the area code. What the user has to decide is whether to screen universally or to build state-conditional logic on top of location data they can defend — the first is cheaper than it looks.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is reassigned-number screening unconditional, rather than triggered by a state field you may not hold?",
        "howToCheck": [
          "Decide between screening every number and building state-conditional logic on location data you can defend. The first is cheaper than it looks.",
          "It is the same lookup the federal safe harbour needs, so doing it universally satisfies both."
        ],
        "failureLooksLike": "Maine converts the federal safe harbour into a duty. National programmes miss it because the federal framing — screen if you want the defence — is the one everybody read."
      }
    },
    {
      "id": "OPS-089",
      "slug": "ops-089",
      "title": "A ported number is a new subscriber",
      "statement": "A fresh opt-in must be obtained when a subscriber ports their number between carriers.",
      "rationale": "Porting is one of the routes by which the person behind a number changes without any signal in the brand's own data, and short-code programmes in particular are expected to re-permission across it. Nothing in the sending platform surfaces a port, so a list treats the number as continuously consenting while the handset it reaches may have changed hands entirely.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "subscriber port event against consent status",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "MMA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where your platform exposes port or carrier-change events, treat one as expiring the consent and re-confirm before the next send. Where it does not, lean on the deactivation feeds and the reassigned-numbers query, which catch most of the same population. Done when a carrier change does not silently preserve consent.",
      "notes": "A carrier event we never see. What the user has to check is whether their provider surfaces porting at all — most do not, and where that is true this obligation is discharged in practice by OPS-083 and OPS-087 rather than directly.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your provider surface porting or carrier-change events at all?",
        "howToCheck": [
          "Ask. Most do not.",
          "Where it does, treat a port as expiring the consent and re-confirm before the next send.",
          "Where it does not, lean on the deactivation feeds and the reassigned-numbers query, which catch most of the same population."
        ],
        "failureLooksLike": "Nothing in the sending platform surfaces a port, so the list treats the number as continuously consenting while the handset it reaches may have changed hands entirely."
      }
    },
    {
      "id": "OPS-090",
      "slug": "ops-090",
      "title": "Check the number is a live mobile before paying to send to it",
      "statement": "Destination numbers should be validated as real, active mobile numbers before sending, since platforms bill accepted requests regardless of delivery.",
      "rationale": "Billing happens on acceptance, not delivery, so a list full of landlines, disconnected numbers and typos costs money on every send and returns nothing. It is also a quality signal the carriers read: a high proportion of invalid destinations is one of the shapes that distinguishes a purchased list from an organic one, so the waste and the reputational damage arrive together.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "destination number validation",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Run a lookup against the list before a large send and drop landlines, invalid formats and disconnected numbers. Done when the invalid rate on a send is measured rather than assumed.",
      "example": "Pre-send lookup: 40,000 numbers → 38,900 mobile, 700 landline, 400 invalid → send to 38,900.",
      "notes": "A paid per-number lookup at the provider. What the user has to weigh is the lookup cost against the per-message cost of sending to numbers that cannot receive — for a list built from typed-in phone fields the lookup usually pays for itself on the first send.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is the invalid rate on your sends measured, rather than assumed?",
        "howToCheck": [
          "Run a lookup against the list before a large send and drop landlines, invalid formats and disconnected numbers.",
          "Weigh the per-number lookup cost against the per-message cost of sending to numbers that cannot receive — for a list built from typed-in phone fields it usually pays for itself on the first send."
        ],
        "failureLooksLike": "Billing happens on acceptance, not delivery, so the waste is invisible in the delivery metrics. A high proportion of invalid destinations is also one of the shapes that distinguishes a purchased list from an organic one."
      }
    },
    {
      "id": "OPS-092",
      "slug": "ops-092",
      "title": "Local time is where the recipient is, not what their area code says",
      "statement": "The recipient's local time must be derived from their actual location, not from the area code of their number.",
      "rationale": "Numbers are portable and people move, so area codes stopped being geography years ago — a 212 number in Los Angeles is common, and a programme timing sends off it will message that person at 5am. This one assumption breaks every timing rule at once, and it is the default in almost every scheduler because the area code is the only location the phone number offers.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "recipient location resolution",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "state law"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Time sends from a location you actually hold — the billing or shipping address, the account's stated location, the timezone the app reports — and fall back to a conservative window rather than to the area code where you hold none. Done when a customer with an out-of-state number is messaged on their own clock.",
      "example": "Schedule against profile.timezone where set; where unset, send only between 10am and 7pm Eastern, which is inside every US window.",
      "pitfalls": [
        "A conservative national window is a legitimate answer and often the right one — the narrow band that satisfies every state is a few hours, and most programmes do not need more."
      ],
      "notes": "Scheduler logic we cannot see. What the user has to decide is what location data they hold and are willing to rely on, because the alternative is not the area code — it is a narrower universal window.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "What location do you actually hold for each recipient — and is a customer with an out-of-state number messaged on their own clock?",
        "howToCheck": [
          "Decide which location data you hold and will rely on: the billing or shipping address, the account's stated location, the timezone the app reports.",
          "Where you hold none, fall back to a conservative national window rather than to the area code.",
          "The narrow band that satisfies every state is a few hours wide and most programmes do not need more."
        ],
        "failureLooksLike": "A 212 number in Los Angeles is common, and a scheduler timing off the area code messages that person at 5am. It is the default in almost every scheduler, because the area code is the only location a phone number offers."
      }
    },
    {
      "id": "OPS-093",
      "slug": "ops-093",
      "title": "Several states set a narrower quiet-hours window than the federal one",
      "statement": "Where the recipient's state sets a narrower window than 8am–9pm, that window applies.",
      "rationale": "The federal window is the widest one available, not the operative one: Florida, Oklahoma, Maryland, Washington and Oregon close at 8pm, and Connecticut does not open until 9am — the narrowest band in the country. A national send timed to the federal window is lawful for most recipients and a statutory-damages claim for the rest, and several of these statutes carry a private right of action specifically so that it is worth bringing.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "send time against the recipient state window",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (FL, OK, MD, WA, OR, CT, NY, NJ, VA)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Either hold a per-state window table and reliable recipient location, or send only inside the intersection of every state window — 9am to 8pm local — which needs no state data at all. Done when no send can fall outside the narrowest applicable window.",
      "example": "National promotional window: 9:00am–8:00pm recipient local time, which clears every state rule at once.",
      "notes": "Absorbs OPS-094 and OPS-095, which are two more rows of the same per-state table. Needs the recipient's state, which we do not hold. What the user has to choose is between building the table and adopting the intersection — the intersection costs an hour at each end of the day and removes the whole class of failure.",
      "catalogIds": [
        "OPS-094",
        "OPS-095"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can any send fall outside 9am–8pm in the recipient's local time?",
        "howToCheck": [
          "Choose between a per-state window table with reliable recipient location, and sending only inside the intersection of every state window — 9am to 8pm local, which needs no state data at all.",
          "Florida, Oklahoma, Maryland, Washington and Oregon close at 8pm; Connecticut does not open until 9am."
        ],
        "failureLooksLike": "A national send timed to the federal 8am–9pm window is lawful for most recipients and a statutory-damages claim for the rest — and several of these statutes carry a private right of action precisely so it is worth bringing."
      }
    },
    {
      "id": "OPS-096",
      "slug": "ops-096",
      "title": "A state may black out whole days",
      "statement": "Where a state enacts holiday blackout dates for solicitations, no solicitation may be sent on them.",
      "rationale": "A blackout is not a narrower window, it is a day on which the send must not happen at all — so a scheduler that only knows about hours will happily run a campaign into a prohibited day. These are recent and expanding statutes, which means a programme built correctly two years ago can become non-compliant without anything about it changing.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "send date against a state holiday calendar",
      "severity": "LOW",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (UT)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Hold a blackout calendar alongside the window table and check the date as well as the time before scheduling. Done when a campaign scheduled across a blackout date skips the affected recipients rather than the whole send.",
      "notes": "State calendars we do not hold, and a list that changes by legislature. What the user has to do is subscribe to a compliance feed or review the statutes annually — this is the rule most likely to have changed since anybody last looked.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do you hold a current state holiday blackout calendar, and when was it last reviewed?",
        "howToCheck": [
          "Subscribe to a compliance feed or review the statutes annually — this is the rule most likely to have changed since anybody last looked.",
          "Check the date as well as the time before scheduling, so a campaign crossing a blackout skips the affected recipients rather than the whole send."
        ],
        "failureLooksLike": "A blackout is a day on which the send must not happen at all, so an hours-only scheduler runs the campaign into it. A programme built correctly two years ago becomes non-compliant with nothing about it changing."
      }
    },
    {
      "id": "OPS-097",
      "slug": "ops-097",
      "title": "Some states cap how often you may raise the same subject",
      "statement": "Where the recipient's state sets one, a per-subject frequency cap — commonly three messages per 24 hours on the same subject matter — must be enforced.",
      "rationale": "The cap is per subject rather than per programme, which is the part that catches people: a cart reminder, a discount on the same item and a low-stock alert are three messages about one thing, sent by three different automations that each believe they are within their own limit. Nothing in a per-campaign frequency setting can see that.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-recipient send count by subject, 24-hour window",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (FL, OK, MD, NJ, OR)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Count sends per recipient per subject across every automation rather than per campaign, and cap at three in 24 hours for the states that set one — or universally, which is simpler and rarely costs anything. Done when three automations about one product cannot all fire on the same day.",
      "notes": "A rolling count across systems we cannot see. What the user has to build is a subject taxonomy — without one there is nothing to count against, and per-campaign counters will report compliance while the cap is being broken.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could three automations about one product all fire to the same person on the same day?",
        "howToCheck": [
          "Build a subject taxonomy first — without one there is nothing to count against, and per-campaign counters will report compliance while the cap is broken.",
          "Count sends per recipient per subject across every automation, and cap at three in 24 hours universally rather than per state."
        ],
        "failureLooksLike": "A cart reminder, a discount on the same item and a low-stock alert are three messages about one thing, sent by three automations that each believe they are within their own limit."
      }
    },
    {
      "id": "OPS-098",
      "slug": "ops-098",
      "title": "There is a per-recipient daily ceiling even without a state rule",
      "statement": "No more than ten messages should reach one recipient in any 24-hour period without two-way engagement or explicit high-frequency consent.",
      "rationale": "Providers apply this as an acceptable-use limit regardless of state law, and crossing it reads as a malfunction rather than a campaign — which is usually what it is. The classic cause is a loop: an automation that fires on a status change, and a status that flaps.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-recipient 24-hour send count",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Cap per-recipient daily volume across all automations and alarm when the cap is hit, since hitting it usually means something is looping. Done when a runaway automation is stopped by the cap rather than by a complaint.",
      "example": "Per-recipient limit 10 per 24h across every campaign; breach pauses the automation and pages the owner.",
      "notes": "A counter across the whole account. What the user has to check is that the cap spans campaigns rather than sitting inside one — per-campaign caps are the common implementation and they do not catch the loop.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your per-recipient daily cap span every campaign in the account, or sit inside one?",
        "howToCheck": [
          "Check where the counter lives. Per-campaign caps are the common implementation and they do not catch a loop.",
          "Cap per-recipient daily volume across all automations at ten, and alarm when it is hit — hitting it usually means something is looping."
        ],
        "failureLooksLike": "An automation fires on a status change and the status flaps. Crossing the ceiling reads as a malfunction rather than a campaign, which is usually exactly what it is."
      }
    },
    {
      "id": "OPS-099",
      "slug": "ops-099",
      "title": "A state may cap monthly volume and demand the logs",
      "statement": "Where a state sets monthly solicitation volume caps and log-production duties, both must be met.",
      "rationale": "Tennessee caps monthly solicitations and requires call logs produced every six months, which is an obligation with a filing deadline rather than a technical control — and a programme that has never produced the logs cannot produce them retrospectively if it did not retain the fields. The volume cap is low enough that a single national campaign can exceed it in one send.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "monthly solicitation volume and log production",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (TN)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Count solicitations per state per month, and retain per-message logs in a form you can hand over. Done when you could produce six months of logs for one state on request without an engineering project.",
      "notes": "A state filing obligation. What the user has to establish is whether they solicit into the state at all, and if so, that their message logs carry the fields the production requires — OPS-216 covers the log content.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could you produce six months of per-message logs for one state on request, without an engineering project?",
        "howToCheck": [
          "Establish whether you solicit into a state with volume caps and log-production duties — Tennessee is the named one.",
          "Count solicitations per state per month.",
          "Check your message logs carry the fields the production requires, and retain them in a form you can hand over."
        ],
        "failureLooksLike": "The volume cap is low enough that a single national campaign can exceed it in one send, and logs cannot be produced retrospectively if the fields were never retained."
      }
    },
    {
      "id": "OPS-100",
      "slug": "ops-100",
      "title": "A national campaign needs a per-state matrix, evaluated against the strictest rule",
      "statement": "A per-state matrix of quiet hours, frequency caps, consent standards and retention windows must be maintained, and every national campaign evaluated against the most restrictive rule that applies to it.",
      "rationale": "This is the umbrella the individual state rows hang from, and without it each one is checked in isolation by whoever remembered it. A national send silently violates the narrowest state on every axis at once — window, cap, consent standard — and no single rule surfaces that, because each of them is satisfied for most recipients.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-state rule matrix",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Hold one table, state by state, of the window, the cap, the consent standard and the retention floor, and either apply per-state or take the strictest row as the national default. Review it against legislative changes at least annually. Done when a single table answers \"may we send this, there, now\".",
      "pitfalls": [
        "Taking the strictest row nationally is a legitimate and much cheaper answer — the cost is a narrower sending window, not a narrower audience."
      ],
      "notes": "A legal research artefact rather than a system, and it goes stale: state mini-TCPAs are actively being enacted. What the user has to own is the review cadence and the source they trust for it, because the matrix is only as current as its last update.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does a single table answer \"may we send this, there, now\" — and when was it last reviewed against new legislation?",
        "howToCheck": [
          "Hold one table, state by state, of the window, the frequency cap, the consent standard and the retention floor.",
          "Either apply per-state or take the strictest row as the national default — the cost of the second is a narrower sending window, not a narrower audience.",
          "Name the source you trust and the review cadence. State mini-TCPAs are actively being enacted."
        ],
        "failureLooksLike": "Each state row is checked in isolation by whoever remembered it, so a national send violates the narrowest state on every axis at once and no single rule surfaces it."
      }
    },
    {
      "id": "OPS-101",
      "slug": "ops-101",
      "title": "Default to daytime hours even where nothing requires it",
      "statement": "Sending should be restricted to normal daytime business hours in the recipient's local time as a platform default.",
      "rationale": "The legal windows are the outer edges, and sending at their boundary is both lawful and a reliable way to generate complaints — a marketing message at 8:55pm is inside the federal window and reads as unacceptable to the person receiving it. Complaint volume is a carrier metric with consequences of its own, so the conservative default protects deliverability rather than only compliance.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "default scheduled send window",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the platform default to business hours and require a deliberate override to send outside it. Done when sending at the edge of the legal window is a decision somebody made rather than the default.",
      "example": "Platform default 10:00–19:00 recipient local; anything outside requires an explicit per-campaign override.",
      "notes": "A scheduler default we cannot see. What the user has to do is set it once — the value of this rule is that it makes every other timing rule harder to breach by accident.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is sending at the edge of the legal window a decision somebody made, or your platform's default?",
        "howToCheck": [
          "Set the platform default to normal business hours in the recipient's local time.",
          "Require a deliberate override to send outside it."
        ],
        "failureLooksLike": "A marketing message at 8:55pm is inside the federal window and reads as unacceptable to the person receiving it — and complaint volume is a carrier metric with consequences of its own."
      }
    },
    {
      "id": "OPS-102",
      "slug": "ops-102",
      "title": "The national do-not-call registry must be scrubbed before any solicitation",
      "statement": "Recipient lists must be scrubbed against the national do-not-call registry before any telephone solicitation, unless prior express written consent or an established business relationship applies.",
      "rationale": "Registry violations carry per-message statutory damages and are the single most litigated part of the framework, and the exemptions are narrower than operators assume — an established business relationship expires, and \"we have their number\" is not express written consent. The scrub is what converts an argument about the exemption into a list that never contained the number.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "recipient list against the national registry",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC",
        "FTC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Subscribe to the registry for the area codes you message, scrub before every solicitation send, and keep the scrub record with its registry version date. Done when a solicitation list has been through a scrub you can evidence, not merely through a consent check.",
      "pitfalls": [
        "Relying on consent to skip the scrub is defensible only where the consent is genuinely express and written; scrubbing anyway costs little and removes the argument."
      ],
      "notes": "A federal subscription we cannot hold for the brand. What the user has to do is register for access, pay the per-area-code fee, and decide which of their sends are solicitations — the classification is theirs, and getting it wrong in the permissive direction is where the exposure sits. OPS-109 covers documenting the process.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Which of your sends are solicitations — and has each of those lists been through a national registry scrub you can evidence?",
        "howToCheck": [
          "Classify your sends. The classification is yours, and getting it wrong in the permissive direction is where the exposure sits.",
          "Subscribe to the registry for the area codes you message, and scrub before every solicitation send.",
          "Keep the scrub record with its registry version date."
        ],
        "failureLooksLike": "The exemptions are narrower than operators assume — an established business relationship expires, and \"we have their number\" is not express written consent. Relying on consent to skip the scrub leaves an argument where a clean list would have left none."
      }
    },
    {
      "id": "OPS-103",
      "slug": "ops-103",
      "title": "A registry snapshot older than a month is not a scrub",
      "statement": "The national registry version used for a scrub must be no more than 31 days old at the time the message is sent.",
      "rationale": "The registry changes daily, so a stale snapshot removes the people who registered before your download and none of the people who registered after it — and the freshness requirement exists precisely because a scrub against a year-old file looks identical in the logs to a scrub against yesterday's. The failure mode is a pipeline that downloads once and caches indefinitely because nothing ever errors.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "registry snapshot age at send time",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Record the registry version date on every scrub and refuse the send when it is over 31 days old rather than warning about it. Done when an out-of-date snapshot blocks a send instead of producing a log line.",
      "example": "Scrub record: registry_version 2026-03-01, scrubbed_at 2026-03-04, send_at 2026-03-04 — 3 days old, allowed.",
      "notes": "A property of a pipeline we cannot see. What the user has to check is whether the refresh is scheduled and monitored — an unmonitored download that started failing silently is the usual cause, and the scrub keeps reporting success throughout.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does an out-of-date registry snapshot block a send, or only produce a log line?",
        "howToCheck": [
          "Record the registry version date on every scrub and refuse the send when it is over 31 days old.",
          "Check the refresh is scheduled and monitored — an unmonitored download that started failing silently is the usual cause."
        ],
        "failureLooksLike": "The pipeline downloads once and caches indefinitely because nothing ever errors. A scrub against a year-old file looks identical in the logs to a scrub against yesterday's, and the scrub keeps reporting success throughout."
      }
    },
    {
      "id": "OPS-104",
      "slug": "ops-104",
      "title": "Several states run their own do-not-call registry",
      "statement": "Where a state operates a registry that is not fully merged with the federal one, recipient lists must be scrubbed against it as well.",
      "rationale": "A national programme that scrubs only the federal registry is unscrubbed in the states that kept their own, and two of those states are unusually expensive: Texas carries elevated penalties, and Arizona's coverage of text messages specifically is a question worth answering before sending rather than after. The gap is invisible because the federal scrub reports success either way.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "recipient list against state registries",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (TX, AZ and others)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the states you message into, check which of them still run a separate registry, and add those scrubs to the same pre-send step as the federal one. Done when the scrub step names every registry it consulted rather than \"the DNC list\".",
      "notes": "Absorbs OPS-111 and OPS-112, which name Texas and Arizona specifically. Each state registry is a separate subscription with its own access process, and none of them is visible from here. What the user has to do is confirm coverage state by state — including whether the state registry covers texts at all, which is not uniform.",
      "catalogIds": [
        "OPS-111",
        "OPS-112"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your scrub step name every registry it consulted, rather than \"the DNC list\"?",
        "howToCheck": [
          "List the states you message into and check which still run a separate registry — Texas carries elevated penalties and Arizona's coverage of texts specifically is worth settling before sending.",
          "Add those scrubs to the same pre-send step as the federal one. Each is a separate subscription with its own access process.",
          "Confirm state by state whether the registry covers texts at all. It is not uniform."
        ],
        "failureLooksLike": "A national programme that scrubs only the federal registry is unscrubbed in the states that kept their own — and the federal scrub reports success either way."
      }
    },
    {
      "id": "OPS-105",
      "slug": "ops-105",
      "title": "The internal do-not-call list is checked on every send",
      "statement": "Every send must be scrubbed against the brand's own company-specific do-not-call list.",
      "rationale": "The internal list holds the people who told this brand directly to stop, which makes messaging them the most obviously indefensible version of the failure — and unlike the federal registry there is no exemption, no relationship carve-out and no argument available. It gets skipped because the internal list often lives in the CRM rather than in the sending platform, so the send pipeline never consults it.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "recipient list against the internal do-not-call list",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the internal list where the send pipeline can read it, and check it at send time on every campaign — not only on solicitations. Done when a number on the internal list cannot be reached by any campaign the brand runs.",
      "notes": "A list inside the brand, consulted by a pipeline we cannot see. What the user has to verify is that the internal list and the SMS suppression list are the same list, or are synchronised both ways — where they are separate, one of them is always stale. OPS-044 covers the brand-wide scope this depends on.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Are the internal do-not-call list and the SMS suppression list the same list, or synchronised both ways?",
        "howToCheck": [
          "Find where the internal list lives. It is usually the CRM, which is why the send pipeline never consults it.",
          "Put it where the pipeline can read it and check it at send time on every campaign, not only on solicitations.",
          "Where the two lists are separate, establish which one is stale — one of them always is."
        ],
        "failureLooksLike": "These are the people who told this brand directly to stop. There is no exemption, no relationship carve-out and no argument available."
      }
    },
    {
      "id": "OPS-107",
      "slug": "ops-107",
      "title": "Staff have to be trained on the internal list, and it has to be evidenced",
      "statement": "Personnel must be trained in the existence and use of the internal do-not-call list, and the training records retained.",
      "rationale": "The internal list only works if the people who receive the requests know it exists and where to put them, and the training record is what turns a policy into something the brand can show it operates. In an enforcement action the absence of records is treated as the absence of training, whatever actually happened.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "training records",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Train everyone who takes customer contact on how to record a do-not-call request, and keep a dated record of who was trained and when. Done when you can produce the roster for any date in the last five years.",
      "notes": "Records inside the brand that we never see. What the user has to do is keep them for the same period as the suppression records themselves — five years — because the two are asked for together.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could you produce the training roster for any date in the last five years?",
        "howToCheck": [
          "Train everyone who takes customer contact on how to record a do-not-call request.",
          "Keep a dated record of who was trained and when, for five years — the same period as the suppression records, because the two are asked for together."
        ],
        "failureLooksLike": "In an enforcement action the absence of records is treated as the absence of training, whatever actually happened."
      }
    },
    {
      "id": "OPS-108",
      "slug": "ops-108",
      "title": "A do-not-call request is recorded when it is made",
      "statement": "A do-not-call request must be recorded, with the name and number, at the time the request is made.",
      "rationale": "The obligation is about latency rather than storage: a request written up at the end of the week is a week in which the number can still be messaged, and the record then shows a delay nobody can explain. It is also the moment the identifying detail is available — the person is on the phone — and reconstructing a name from a number afterwards is exactly what does not happen.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "internal do-not-call write latency",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the request recordable during the contact — a field the agent sets before ending the call, not a form filed later — and write the name and number together. Done when the record timestamp matches the contact timestamp.",
      "example": "Agent sets Do Not Call on the account during the call: name, number, request received 2026-03-04T11:02Z.",
      "notes": "A workflow inside the brand. What the user has to check is whether their process asks anybody to do this after the fact; batch entry at the end of a shift is common and it is what creates the latency this rule is about.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does the record timestamp on a do-not-call request match the contact timestamp?",
        "howToCheck": [
          "Trace the workflow: is anyone asked to enter these after the fact? Batch entry at the end of a shift is common and it is what creates the latency.",
          "Make the request recordable during the contact — a field the agent sets before ending the call — and capture the name and number together."
        ],
        "failureLooksLike": "A request written up at the end of the week is a week in which the number can still be messaged. The name is available while the person is on the phone and is never reconstructed afterwards."
      }
    },
    {
      "id": "OPS-109",
      "slug": "ops-109",
      "title": "The scrub process needs writing down, version dates included",
      "statement": "The registry access and scrub process must be documented, including the registry version date used for each scrub.",
      "rationale": "The defence to a registry claim is the scrub, and a scrub with no record of which registry version it used cannot answer the only question that matters — whether the number was on the list at the time. Documenting the process is also what makes the freshness requirement auditable rather than aspirational.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "scrub process documentation",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write down who holds the registry subscription, how often it is refreshed, where the scrub runs in the send pipeline, and store the version date with every scrub result. Done when any past send can be tied to the registry version that cleared it.",
      "example": "Scrub log: send acme-2026-03-04, registry_version 2026-03-01, 40,000 in, 312 removed.",
      "notes": "Documentation and logs inside the brand. What the user has to do is keep the version date specifically — most scrub tooling logs a result and a count and drops the version, which leaves the record unable to answer the question it exists for.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can any past send be tied to the registry version that cleared it?",
        "howToCheck": [
          "Write down who holds the registry subscription, how often it is refreshed, and where the scrub runs in the send pipeline.",
          "Store the version date with every scrub result — most scrub tooling logs a result and a count and drops the version."
        ],
        "failureLooksLike": "A scrub with no record of which registry version it used cannot answer the only question that matters: whether the number was on the list at the time."
      }
    },
    {
      "id": "OPS-110",
      "slug": "ops-110",
      "title": "Some states require the seller to register before texting residents",
      "statement": "Before texting a state's residents, determine whether the seller must register with that state and post a bond.",
      "rationale": "Texas requires telephone solicitors to register with the Secretary of State, per location, with a bond — an obligation that has nothing to do with the carriers and that no part of the 10DLC process mentions. It is discovered through an enforcement letter rather than a rejection, and the registration cannot be applied retrospectively to the sends already made.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "state seller registration",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (TX)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the seller-registration requirement for every state you solicit into before the first send, and register where it applies or exclude the state until you have. Done when the states you message are the states you are permitted to solicit in.",
      "notes": "A state filing, entirely outside the messaging stack. What the user has to do is take legal advice on which of their sends count as solicitations and which states they reach — carrier approval says nothing about either, and this is the clearest case in the framework where approval is not permission.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "For every state you solicit into, have you checked whether the seller must register and post a bond there?",
        "howToCheck": [
          "Take legal advice on which of your sends count as solicitations and which states they reach.",
          "Texas requires telephone solicitors to register with the Secretary of State, per location, with a bond.",
          "Register where it applies, or exclude the state until you have."
        ],
        "failureLooksLike": "No part of the 10DLC process mentions it, so it arrives as an enforcement letter rather than a rejection — and the registration cannot be applied retrospectively to sends already made."
      }
    },
    {
      "id": "OPS-113",
      "slug": "ops-113",
      "title": "A state may impose its own opt-out and identification duties",
      "statement": "Where a state requires it, opt-out replies must be honoured and accurate sender identification maintained under that state's own rules.",
      "rationale": "Virginia and states like it restate obligations the federal rules already impose, which sounds redundant and is not: the state version comes with its own enforcement route and its own penalties, so a failure is actionable twice. It matters most for sender identification, where the federal framing is a carrier expectation and the state framing is a statute.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "opt-out handling and sender identification, per state",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (VA)"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Identify the brand in every message and honour every opt-out immediately, which satisfies the state rules and the federal ones together. Done when no message leaves without the brand name in it.",
      "notes": "State law applied to traffic we cannot see. What the user has to do is treat the strictest state as the standard rather than maintaining per-state behaviour — the identification and opt-out duties are cheap to satisfy universally and expensive to get wrong once.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does every message leaving your programme carry the brand name, and is every opt-out honoured immediately?",
        "howToCheck": [
          "Treat the strictest state as the standard rather than maintaining per-state behaviour: identification and immediate opt-out are cheap universally.",
          "Read a real delivered message and confirm the brand is identifiable in it."
        ],
        "failureLooksLike": "Virginia and states like it restate federal duties with their own enforcement route and penalties, so one failure is actionable twice."
      }
    },
    {
      "id": "OPS-114",
      "slug": "ops-114",
      "title": "A programme relying on the delivery exemption still owes an opt-out",
      "statement": "A campaign relying on the package-delivery exemption must offer an opt-out and honour it within six business days.",
      "rationale": "The exemption removes the need for prior express consent, and brands read that as removing the consumer's ability to refuse — which is the one thing it does not do. The FCC attached conditions to every exemption it granted, and the opt-out is the condition that gets dropped, because a delivery notification feels like something nobody would want to stop.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.sample[] + campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Carry opt-out instructions in the exempt messages themselves and state the honouring window in the flow. Done when a delivery notification tells the recipient how to stop and the flow says how quickly that takes effect.",
      "example": "Acme Coffee: your order #4471 ships today, track at acmecoffee.com/t/4471. Reply STOP to stop delivery texts.",
      "notes": "Applies only where a campaign claims one of the FCC's exempted categories, which the registration has no attribute for. The condition sits in the criteria, and the note records that: a campaign that never invokes an exemption is not failed by this rule.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-115",
      "slug": "ops-115",
      "title": "An exempt message must not cost the recipient anything",
      "statement": "Where an exemption requires it, exempted informational messages must not be charged to the called party or counted against their plan limits.",
      "rationale": "Several of the FCC exemptions are conditional on the message being free to the recipient, and a programme relying on the exemption while sending billable messages has not met the condition — so the exemption does not apply and the messages needed consent after all. It is a condition nobody controls from the sending side, which is exactly why it has to be checked rather than assumed.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "billing treatment of exempt messages",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm with the carrier or provider whether the traffic is free-to-end-user where the exemption requires it, and stop relying on that exemption where it is not. Done when the exemption you are relying on has had each of its conditions checked rather than only its category.",
      "notes": "Determined by the carrier and the recipient's plan, neither of which we see. What the user has to do is read the conditions attached to the specific exemption they are claiming — the category is the famous part and the conditions are where these fail.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "For the exemption you are relying on, has each of its conditions been checked — including whether the message is free to the recipient?",
        "howToCheck": [
          "Read the conditions attached to the specific exemption, not just its category. The category is the famous part and the conditions are where these fail.",
          "Confirm with the carrier or provider whether the traffic is free-to-end-user where the exemption requires it.",
          "Stop relying on that exemption where it is not."
        ],
        "failureLooksLike": "The programme sends billable messages under an exemption conditional on them being free. The condition is unmet, so the exemption does not apply and the messages needed consent after all."
      }
    },
    {
      "id": "OPS-116",
      "slug": "ops-116",
      "title": "Carrier approval is not legal cover",
      "statement": "Litigation exposure must be sized separately for state regimes with a private right of action, and carrier approval must not be treated as legal compliance.",
      "rationale": "The two systems are decided by different bodies against different standards: carriers decide whether traffic is carried, courts decide whether it was lawful, and an approved campaign is routinely both. States with private rights of action and statutory damages per message turn a modest programme into a material liability, and the approval that arrived first is not evidence of anything in that forum.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "compliance posture and risk model",
      "severity": "LOW",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "state law (GA, WA and others)",
        "courts"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Size the exposure per state — messages sent, statutory damages, class exposure — and treat that as the compliance driver rather than the carrier verdict. Done when somebody can state the worst-case number for the states you message into.",
      "notes": "A legal assessment rather than a check. What the user has to do is get advice on the states they reach, and separate that conversation from anything about registration — the two get conflated because approval feels like permission and is not.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can somebody state the worst-case statutory-damages number for the states you message into?",
        "howToCheck": [
          "Size the exposure per state: messages sent, statutory damages per message, class exposure.",
          "Get legal advice on the states you reach, and keep that conversation separate from anything about registration.",
          "Treat that number as the compliance driver rather than the carrier verdict."
        ],
        "failureLooksLike": "Carriers decide whether traffic is carried and courts decide whether it was lawful. An approved campaign is routinely both, and approval feels like permission when it is not."
      }
    },
    {
      "id": "OPS-123",
      "slug": "ops-123",
      "title": "Every message needs a valid carrier message-class tag",
      "statement": "Every 10DLC A2P message must carry a valid, authorised carrier message class tag.",
      "rationale": "The class tag is how AT&T decides what throughput the message gets and whether it is carried at all, and it is assigned to the campaign rather than chosen per send — so traffic sent with the wrong tag, or none, fails in a way that looks like a throughput problem rather than a tagging one. It is set by the platform, which is why the sender rarely knows it exists until delivery is unexplainably poor.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound message class tag",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AT&T",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm with your provider which class the campaign was assigned and that outbound traffic carries it. Done when you can state your campaign's message class and it matches what the provider registered.",
      "notes": "Applied in the sending path. What the user has to do is ask — the class is visible in the campaign record at the provider, and mismatches between the assigned class and the traffic are diagnosed there rather than in any error code.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can you state your campaign's carrier message class, and does the outbound traffic carry it?",
        "howToCheck": [
          "Look the class up in the campaign record at your provider — it is assigned to the campaign, not chosen per send.",
          "Confirm with them that outbound traffic carries it.",
          "Mismatches are diagnosed there rather than in any error code."
        ],
        "failureLooksLike": "The wrong tag, or none, fails in a way that looks like a throughput problem rather than a tagging one — and it is set by the platform, so the sender rarely knows it exists until delivery is unexplainably poor."
      }
    },
    {
      "id": "OPS-124",
      "slug": "ops-124",
      "title": "Unregistered and P2P-classified traffic has a hard per-number ceiling",
      "statement": "Traffic classified as P2P or unregistered is limited to roughly fifteen messages or segments per number per minute.",
      "rationale": "The ceiling is low enough that any real campaign hits it immediately, which is the point: it is what makes registration the only viable path. Senders meet it while testing before their campaign is approved and conclude the platform is slow, then build a sending schedule around a limit that would have gone away.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-number per-minute send rate",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Do not size a programme against unregistered throughput. Wait for the campaign to be approved and provisioned before measuring what the number can do. Done when the rate you planned against is the registered one.",
      "notes": "A carrier-applied rate we cannot observe. What the user has to check is whether the numbers they benchmarked on were attached to an approved campaign at the time — benchmarks taken before provisioning are the usual source of a wrong plan.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Were the numbers you benchmarked throughput on attached to an approved campaign at the time?",
        "howToCheck": [
          "Check when the benchmark was taken against when the campaign was provisioned.",
          "Unregistered and P2P-classified traffic is capped at roughly fifteen messages or segments per number per minute — low enough that any real campaign hits it immediately.",
          "Re-measure after provisioning, and plan against that number."
        ],
        "failureLooksLike": "A team tests before approval, concludes the platform is slow, and builds a sending schedule around a limit that would have gone away."
      }
    },
    {
      "id": "OPS-126",
      "slug": "ops-126",
      "title": "A low-volume campaign has a ceiling, not a warning",
      "statement": "Low-volume campaigns must stay under their published ceiling, or be upgraded to a standard registration.",
      "rationale": "The tier is priced for genuinely small programmes, and exceeding it does not throttle politely — the traffic above the ceiling is unregistered for the purposes of the carriers who applied it. Programmes grow into this without noticing, because the registration that was right at launch says nothing when the list doubles.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign volume against the use-case ceiling",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "TCR",
        "Telnyx",
        "Bird"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Track monthly volume against the tier ceiling and upgrade before crossing it rather than after. Done when the registration matches the traffic the programme is actually sending this quarter.",
      "notes": "Volume against a published ceiling. What the user has to own is the review — nothing prompts an upgrade, and the registration keeps looking correct while the traffic outgrows it. OPS-VOLUME-PLAUSIBLE checks the declaration at registration time.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your low-volume registration still match the traffic the programme is sending this quarter?",
        "howToCheck": [
          "Track monthly volume against the tier ceiling and own the review — nothing prompts an upgrade.",
          "Upgrade before crossing it rather than after."
        ],
        "failureLooksLike": "The registration that was right at launch says nothing when the list doubles. Traffic above the ceiling is unregistered for the purposes of the carriers who applied it, rather than politely throttled."
      }
    },
    {
      "id": "OPS-127",
      "slug": "ops-127",
      "title": "UCaaS low-volume carries restrictions beyond the volume",
      "statement": "A UCaaS low-volume registration is limited to a low daily ceiling, a restricted throughput class, one number per human user, and no API or automated traffic.",
      "rationale": "The tier exists for individual humans typing messages, and the conditions enforce that rather than merely the volume — so a business that stays under the daily cap but sends through an API has still breached it. It is chosen for the low cost and the conditions are discovered afterwards.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "UCaaS traffic pattern against the tier conditions",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use this tier only for human-sent, one-number-per-person messaging. Register a standard campaign for anything automated, however low the volume. Done when no API traffic runs on a UCaaS registration.",
      "notes": "Tier conditions applied by the carriers. What the user has to confirm is that nothing automated touches the numbers on this tier — an integration somebody added later is the usual breach, and it is invisible in the volume figures.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does anything automated touch the numbers on your UCaaS low-volume registration?",
        "howToCheck": [
          "Look for API traffic, integrations and scheduled sends on those numbers — an integration somebody added later is the usual breach, and it is invisible in the volume figures.",
          "The tier requires human-sent messages, one number per person, no automation — staying under the daily cap is not sufficient.",
          "Register a standard campaign for anything automated, however low the volume."
        ],
        "failureLooksLike": "A business well under the daily ceiling has still breached the tier because it sends through an API. The tier was chosen for the price and the conditions were read afterwards."
      }
    },
    {
      "id": "OPS-128",
      "slug": "ops-128",
      "title": "Registering and vetting a brand does not raise the platform rate limit",
      "statement": "Brand registration and vetting alone do not raise the sending platform's own default per-number rate; a separate increase must be requested.",
      "rationale": "There are two ceilings — the carrier's and the platform's — and vetting moves only the first. Teams complete an expensive vet, see no change in throughput, and conclude the vet failed or the carrier is ignoring it, when the constraint is a default on their own account that nobody has asked to change.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "platform account sending rate",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Raise a rate-increase request with the platform as a separate task from vetting, and check the account limit before diagnosing a throughput problem as a carrier one. Done when the platform limit is above the throughput the campaign was granted.",
      "notes": "An account limit at the provider. What the user has to do is look it up before spending on a vet for throughput reasons — the platform default is frequently the binding constraint and much cheaper to move.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Have you checked your platform's own per-number rate limit before blaming the carrier — or before buying a vet for throughput?",
        "howToCheck": [
          "Look up the account limit at your provider. There are two ceilings, the carrier's and the platform's, and vetting moves only the first.",
          "Raise a rate-increase request with the platform as a separate task from vetting.",
          "Done when the platform limit is above the throughput the campaign was granted."
        ],
        "failureLooksLike": "An expensive vet completes, throughput does not move, and the team concludes the vet failed — when the binding constraint was a default on their own account that nobody asked to change."
      }
    },
    {
      "id": "OPS-130",
      "slug": "ops-130",
      "title": "Tax-exempt status is not a political vet",
      "statement": "Uncapped political throughput requires a valid political vetting artefact; 501(c) tax-exempt status alone yields only standard terms.",
      "rationale": "Both facts sit on the brand record and one of them looks like the other, so a non-profit running a political programme reasonably expects its tax status to carry the throughput. It does not: the carriers want independent verification that the sender is who they claim, and a tax exemption verifies something else entirely.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "political vetting artefact against throughput terms",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "TCR"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Obtain a political vet — Campaign Verify or an equivalent both carriers accept — and import it, if the programme needs uncapped throughput. Done when the throughput you planned against is backed by the artefact that grants it.",
      "notes": "Throughput terms granted by the carriers. What the user has to do is check which artefact their brand actually holds before planning volume; XBC-003 refuses a political campaign with no vetting at all, which is the approval-side question rather than this one.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Which artefact does the brand actually hold — a political vet, or 501(c) tax-exempt status?",
        "howToCheck": [
          "Read the brand record. Both facts sit there and one looks like the other.",
          "Only a political vet — Campaign Verify or an equivalent both carriers accept — grants uncapped political throughput.",
          "Obtain and import one if the programme needs that volume."
        ],
        "failureLooksLike": "A non-profit running a political programme expects its tax status to carry the throughput. The carriers want independent verification that the sender is who they claim, and a tax exemption verifies something else entirely."
      }
    },
    {
      "id": "OPS-131",
      "slug": "ops-131",
      "title": "A validity period shorter than the queue drops messages silently",
      "statement": "The message validity period must be long enough for the message to survive queueing at the sender's actual throughput.",
      "rationale": "Validity period is a per-message expiry, so a large send at a modest throughput will have messages expire in the queue before they are ever attempted — and they are discarded rather than failed, so the delivery report shows nothing wrong. The default is short relative to the time a hundred-thousand-message send takes at fifteen a second.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "message validity period against queue depth",
      "severity": "LOW",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Divide the send size by the granted throughput to get the drain time, and set the validity period comfortably above it. Done when the last message in a large send is still valid when its turn arrives.",
      "example": "200,000 messages at 60/second ≈ 56 minutes to drain → validity period 4 hours, not the 15-minute default.",
      "notes": "A per-message parameter in the sending API. What the user has to check is the arithmetic for their largest send — the default is fine for small ones, which is why the problem only appears at scale.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "At your granted throughput, will the last message of your largest send still be valid when its turn arrives?",
        "howToCheck": [
          "Do the arithmetic: send size divided by granted throughput is the drain time.",
          "Set the validity period comfortably above it. The default is fine for small sends, which is why this only appears at scale."
        ],
        "failureLooksLike": "Messages expire in the queue before they are attempted and are discarded rather than failed, so the delivery report shows nothing wrong at all."
      }
    },
    {
      "id": "OPS-132",
      "slug": "ops-132",
      "title": "The consumer-traffic thresholds are where P2P stops",
      "statement": "Traffic above the published consumer-operation reference thresholds is classified as non-consumer, whatever the sender intends.",
      "rationale": "The thresholds — a rough messages-per-minute band, around a thousand a day, one number per person, a hundred recipients per message, roughly balanced two-way traffic, and a limit on repeated identical messages — define the boundary in practice. A sender operating just above them without an A2P registration is not in a grey area; they are unregistered A2P traffic, and it is blocked outright rather than throttled.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "per-number traffic shape",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Measure your own traffic against the thresholds and register A2P if you are near any of them. The two-way ratio is the one people miss — traffic that is overwhelmingly outbound is non-consumer whatever the volume.",
      "notes": "Traffic shape we cannot see. What the user has to check is the outgoing-to-incoming ratio on their numbers, since it reclassifies traffic that is well under every volume threshold.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "What is the outgoing-to-incoming ratio on your numbers?",
        "howToCheck": [
          "Measure it. Traffic that is overwhelmingly outbound is non-consumer whatever the volume, and it is the threshold people miss.",
          "Check the others too: messages per minute, roughly a thousand a day, one number per person, a hundred recipients per message, repeated identical messages.",
          "Register A2P if you are near any of them."
        ],
        "failureLooksLike": "A sender just above the thresholds without an A2P registration is not in a grey area — they are unregistered A2P traffic, and it is blocked outright rather than throttled."
      }
    },
    {
      "id": "OPS-133",
      "slug": "ops-133",
      "title": "A P2P exemption has conditions, all of them",
      "statement": "A P2P exemption may only be registered where every published condition holds — no business sending, not a cloud communications suite, human-written messages, traffic symmetry no worse than one to three, and a clean compliance history.",
      "rationale": "The exemption is attractive because it removes registration entirely, so it attracts applications from senders who meet most of the conditions. Meeting most of them is meeting none: the exemption is withdrawn on any one, and the traffic that ran under it retrospectively becomes unregistered A2P.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "P2P exemption application",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check every condition honestly before applying, particularly the traffic-symmetry and human-written ones. Where any fails, register instead — the cost of the registration is far below the cost of a withdrawn exemption.",
      "notes": "An application to the provider. What the user has to do is measure their own traffic symmetry first, because it is the condition most often assumed and least often met.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Have you measured your traffic symmetry against the one-to-three condition before applying for a P2P exemption?",
        "howToCheck": [
          "Measure the ratio first. It is the condition most often assumed and least often met.",
          "Then check every other condition honestly: no business sending, not a cloud communications suite, human-written messages, clean compliance history.",
          "Where any fails, register instead."
        ],
        "failureLooksLike": "Meeting most of the conditions is meeting none. The exemption is withdrawn on any one of them, and the traffic that ran under it retrospectively becomes unregistered A2P."
      }
    },
    {
      "id": "OPS-134",
      "slug": "ops-134",
      "title": "The registry has API rate limits of its own",
      "statement": "Integrations must stay within the registry's published API rate limits.",
      "rationale": "A polling integration built without regard to the limits will hit them during exactly the operations that matter — a bulk registration, a status reconciliation after an outage — and the failures look like registry problems rather than client ones. The fix is architectural rather than a retry, which is why it belongs before the integration is written.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "API request rate to the registry",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Back off on rate-limit responses, and replace polling with the status webhooks the registry publishes. Done when a bulk operation completes without a single rate-limit error.",
      "notes": "Integration behaviour at the registry. What the user has to establish is whether their platform polls — most do, and it is the usual cause. OPS-347 covers the webhook alternative.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your platform poll the registry for status, rather than consuming its webhooks?",
        "howToCheck": [
          "Ask, or read the integration. Most poll, and it is the usual cause.",
          "Back off on rate-limit responses and replace polling with the published status webhooks.",
          "Done when a bulk operation completes without a single rate-limit error."
        ],
        "failureLooksLike": "The limits are hit during exactly the operations that matter — a bulk registration, a reconciliation after an outage — and the failures read as registry problems rather than client ones."
      }
    },
    {
      "id": "OPS-135",
      "slug": "ops-135",
      "title": "Do not spread one message across many numbers",
      "statement": "Similar or identical content must not be distributed across many sending numbers to dilute per-number reputation or evade filtering.",
      "rationale": "Filtering is scored per originator, so splitting a send across ten numbers gives each one a tenth of the complaint rate and a tenth of the volume — which is the entire mechanism, and the carriers named it before anybody built a product around it. The innocent version is indistinguishable from the outside: a brand whose sends were slow buys more numbers to go faster, and is now doing exactly what the rule prohibits. It carries a four-figure pass-through fee rather than a rejection, so the first signal is an invoice.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound traffic distribution across numbers",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "CTIA",
        "Twilio",
        "Telnyx",
        "AWS",
        "Vonage",
        "Sinch",
        "Plivo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Route similar content through one registered originator, or request an approved number pool for the campaign so the distribution is declared rather than discovered. Where throughput is the real constraint, raise it through vetting rather than through number count.",
      "pitfalls": [
        "Needing more throughput is a legitimate problem with a legitimate answer — a registered number pool, or a higher-tier brand vet — and neither of them is more numbers quietly added to a campaign."
      ],
      "notes": "Absorbs MSG-138, the same prohibition from the message-content side. A traffic shape we cannot observe. What the user has to check is how many numbers their campaign actually sends from and whether a pool was ever approved for it — OPS-POOL-DECLARED covers the registration-side declaration, and OPS-136 catches the duplicate-campaign version of the same pattern before submission.",
      "catalogIds": [
        "MSG-138"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "How many numbers does this campaign actually send from, and was a pool ever approved for it?",
        "howToCheck": [
          "Count the originators on the campaign, then ask your provider whether an approved number pool is on record.",
          "Where throughput is the real constraint, raise it through vetting rather than through number count.",
          "Route similar content through one registered originator otherwise."
        ],
        "failureLooksLike": "A brand whose sends were slow buys more numbers to go faster and is now doing exactly what the rule prohibits. It carries a four-figure pass-through fee rather than a rejection, so the first signal is an invoice."
      }
    },
    {
      "id": "OPS-136",
      "slug": "ops-136",
      "title": "The campaign must not duplicate another campaign on the account",
      "statement": "A campaign whose description, name, website, message flow and samples all match an existing campaign is a snowshoeing signal.",
      "rationale": "Duplicate campaigns spread identical content across multiple registrations and numbers, which is the definition of snowshoeing and carries a four-figure pass-through fee rather than a rejection. Legitimate multi-campaign brands differentiate by audience or content.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign fields across the account",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Consolidate duplicates into one campaign, or differentiate them by genuine audience and content. Where you need more numbers for volume, request a number pool on one campaign rather than cloning it.",
      "example": "One MARKETING campaign with a number pool, rather than four identical MARKETING campaigns.",
      "notes": "Fully decidable only against other campaigns on the account; today we judge self-similarity within the submission.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-137",
      "slug": "ops-137",
      "title": "Do not burn numbers and replace them",
      "statement": "Numbers must not be cycled — used until deliverability degrades and then discarded, or freshly provisioned to escape a suspension.",
      "rationale": "A number whose delivery is falling is telling you something about the traffic, and replacing it discards the signal rather than the cause. Carriers treat the pattern as deliberate because it is the standard playbook of senders who intend to keep sending regardless — and provisioning a new number while a suspension is open escalates from a campaign problem to an account one.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "number provisioning and retirement pattern",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "Vonage",
        "Plivo",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Treat degrading delivery as a content or consent problem and fix that. Keep the number: recovering its reputation is slow but possible, and a fresh number inherits the same traffic and degrades the same way.",
      "notes": "Visible only in provisioning history over time. What the user has to look at is their own number churn over the last quarter — a pattern nobody planned but that has emerged from individual reasonable decisions is the usual finding.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "What has your number churn been over the last quarter — how many numbers were retired and replaced?",
        "howToCheck": [
          "Pull the provisioning history and count retirements. A pattern nobody planned, emerging from individually reasonable decisions, is the usual finding.",
          "For each retirement, ask what prompted it. Falling delivery is a content or consent signal, not a number fault.",
          "Never provision a fresh number while a suspension is open — that escalates a campaign problem to an account one."
        ],
        "failureLooksLike": "The number is discarded and the traffic that degraded it is not, so the replacement inherits the same content and degrades the same way. Carriers read the pattern as the standard playbook of senders who intend to keep sending regardless."
      }
    },
    {
      "id": "OPS-138",
      "slug": "ops-138",
      "title": "A blocked number must not be automatically replaced",
      "statement": "A number blocked by a receiving network must never be auto-replaced with a fresh one, and a permanent block code must be treated as a programme defect rather than a replacement trigger.",
      "rationale": "Automatically swapping a blocked originator is filter-evasion assistance, and it is named as such — the carrier blocked the number to stop the traffic, and replacing it defeats a control the network applied deliberately. It is almost always built with good intentions, as failover: delivery dropped, so the system routed around it, exactly as it would around a hardware fault.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "blocked-number remediation logic",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make a permanent block stop the campaign and page a human rather than trigger failover. Investigate the content and the consent behind the traffic that earned the block before any number is provisioned.",
      "notes": "Absorbs MSG-139, which states the same rule and adds the dynamic-routing case. Logic inside the sending platform. What the user has to audit is their own failover configuration — a rule that treats a block like an outage is the specific thing to look for, and it is a sensible-looking line in a runbook.",
      "catalogIds": [
        "MSG-139"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "What does your failover configuration do when a receiving network permanently blocks a number?",
        "howToCheck": [
          "Read the failover rules in your platform and your runbook. Look specifically for a rule that treats a block the way it treats an outage — it is a sensible-looking line and it is the defect.",
          "Make a permanent block stop the campaign and page a human instead.",
          "Confirm the investigation starts with the content and the consent behind the blocked traffic, before any number is provisioned."
        ],
        "failureLooksLike": "Delivery dropped, so the system routed around it — exactly as it would around a hardware fault. The carrier blocked that number deliberately, and swapping it is named as filter-evasion assistance."
      }
    },
    {
      "id": "OPS-139",
      "slug": "ops-139",
      "title": "One number, one static route",
      "statement": "Each 10DLC, short code or toll-free number must use a single static route; dynamic routing is permitted only for major network outages.",
      "rationale": "Static routing is what makes traffic attributable — carriers trace a complaint back through the route, and a number whose path changes per message cannot be traced or trusted. Dynamic routing is also how grey routes get used without anybody choosing one, since a least-cost router will find them on its own.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "route configuration per number",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Vonage",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pin each number to one authorised route and disable least-cost or failover routing except during a declared outage. Done when the path a message takes is the same one every time and you can name it.",
      "notes": "Carrier-side routing configuration. What the user has to ask their provider is whether least-cost routing is enabled on their account — it often is by default, and it is the mechanism behind the grey-route rule below.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is least-cost or failover routing enabled on your account — and can you name the single route each number takes?",
        "howToCheck": [
          "Ask your provider directly whether least-cost routing is on. It often is by default.",
          "Pin each number to one authorised route and disable failover except during a declared outage.",
          "Done when the path a message takes is the same one every time and you can name it."
        ],
        "failureLooksLike": "A least-cost router finds a grey route on its own, so nobody chose one and the traffic is on it anyway. A number whose path changes per message cannot be traced back from a complaint."
      }
    },
    {
      "id": "OPS-140",
      "slug": "ops-140",
      "title": "Grey routes are not a price advantage",
      "statement": "A2P traffic must not be carried over any path or setting not authorised by the service providers for non-consumer messaging.",
      "rationale": "A grey route is a path that delivers commercial traffic through consumer-priced channels, and the carriers price it per message once they detect it — at ten dollars a message on at least one network, which turns a saving into a bill nobody budgeted. Senders rarely choose one knowingly: it arrives as a cheap termination option from an intermediary, or as the output of a least-cost router.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "termination path for A2P traffic",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "all MNOs"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send A2P traffic only over the authorised A2P path your provider registers for the campaign, and refuse termination offers priced materially below the market. Ask your provider, in writing, which route your traffic takes.",
      "notes": "A network path we cannot see. What the user has to do is ask their provider to confirm the route in writing and compare their per-message price against the going rate — an unexplained discount is the signal, and the answer arrives as a surcharge rather than as a rejection.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Has your provider confirmed in writing which route your A2P traffic takes — and does your per-message price look like the going rate?",
        "howToCheck": [
          "Ask for the route in writing. Senders rarely choose a grey route knowingly: it arrives as a cheap termination option or as the output of a least-cost router.",
          "Compare your per-message price against the market. An unexplained discount is the signal.",
          "Refuse termination offers priced materially below it."
        ],
        "failureLooksLike": "The answer arrives as a surcharge rather than a rejection — up to ten dollars a message on at least one network, which turns the saving into a bill nobody budgeted."
      }
    },
    {
      "id": "OPS-141",
      "slug": "ops-141",
      "title": "One approved number is controlled by one party",
      "statement": "Sub-aggregation is prohibited: no more than one party may control the content sent from, or the numbers receiving on, a single approved number.",
      "rationale": "The registration says who is behind a number, and sub-aggregation makes that untrue — the consumer receives messages from an entity that was never vetted, and a complaint traces back to a brand that did not send it. It also breaks the one-opt-in-per-message-sender rule underneath, because consent given to one party is being used by another. Agencies and franchise groups arrive here honestly, by provisioning centrally and letting each client send.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "number to content-provider mapping",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Verizon",
        "CTIA",
        "Bird",
        "Vonage"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register a separate brand and campaign for each party whose content goes out, and give each its own numbers. Where a shared arrangement is genuinely needed, get it approved and keep the sender register OPS-142 describes rather than operating it informally.",
      "notes": "Absorbs OPS-266 and MSG-140, which state the same prohibition from the shared-number and message-content sides. Partly visible in what a submission describes and not settleable from it — the arrangement lives in an operating model rather than in a field. What the user has to establish is who can send from each number today, and register a separate brand per party where the answer is more than one. OPS-367 is the ISV form of the same requirement.",
      "catalogIds": [
        "OPS-266",
        "MSG-140"
      ],
      "phase": "post",
      "automated": true
    },
    {
      "id": "OPS-142",
      "slug": "ops-142",
      "title": "A legitimately shared number needs a register of who sends on it",
      "statement": "Where a number or short code is shared under an approved arrangement, documented records of every message sender operating on it must be immediately available.",
      "rationale": "Sharing is permitted only where it can be unpicked, and the register is what unpicks it: a complaint about one message has to resolve to one sender within the time a carrier gives you, which is short. Without it a shared arrangement is indistinguishable from the sub-aggregation the previous rule prohibits, and it will be treated as such.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "shared-number sender register",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep a current list of every sender on the shared number, with the approval that permits the arrangement, somewhere you can produce it the same day. Done when a message can be attributed to a sender without asking anybody.",
      "notes": "A register inside the operator, and no registration field even says the number is shared. What the user has to confirm is that the sharing arrangement was approved in the first place — an undocumented one is OPS-141, not this rule.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "If a number you send from is shared, was the arrangement approved — and could you attribute one message to one sender the same day?",
        "howToCheck": [
          "Confirm the sharing arrangement was approved in the first place. An undocumented one is a different and worse finding.",
          "Keep a current list of every sender on the number, with the approval that permits it, where you can produce it same-day.",
          "Test it: pick a message and attribute it without asking anybody."
        ],
        "failureLooksLike": "A complaint has to resolve to one sender inside the short window a carrier gives you. Without the register, a permitted shared arrangement is indistinguishable from prohibited sub-aggregation and gets treated as such."
      }
    },
    {
      "id": "OPS-143",
      "slug": "ops-143",
      "title": "Distributing like content across several numbers needs an arrangement",
      "statement": "Any use case that genuinely needs multiple numbers to distribute similar content requires special arrangements with the service providers.",
      "rationale": "Some programmes really do need it — regional franchises, multi-location retail — and the answer is not that they cannot, but that it has to be agreed rather than assumed. The arrangement is what separates the legitimate case from snowshoeing, and without it the two are the same traffic shape and get the same treatment.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "multi-number distribution arrangement",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the distribution to the provider before building it and get the arrangement — usually an approved number pool, sometimes a per-location campaign — in writing. Done when the number count on the campaign is one somebody approved.",
      "notes": "The arrangement is made with the provider and we never see it. What the user has to do is raise it before scaling rather than after the fee: providers are generally willing where the case is real, and unwilling once the traffic has already been flagged.",
      "phase": "post",
      "automated": true,
      "attestation": {
        "question": "If your programme genuinely needs several numbers, is the arrangement agreed with your provider in writing?",
        "howToCheck": [
          "Describe the distribution to the provider before building it — regional franchises and multi-location retail are the real cases.",
          "Get the arrangement in writing: usually an approved number pool, sometimes a per-location campaign.",
          "Raise it before scaling. Providers are generally willing where the case is real, and unwilling once the traffic has been flagged."
        ],
        "failureLooksLike": "Without the arrangement, a legitimate multi-location programme and snowshoeing are the same traffic shape and get the same treatment — which arrives as a four-figure fee rather than a conversation."
      }
    },
    {
      "id": "OPS-144",
      "slug": "ops-144",
      "title": "Do not rotate domains across a bulk send",
      "statement": "Multiple fully-qualified domains, or multiple public URL shorteners, must not be cycled across bulk messages carrying similar content.",
      "rationale": "URL rotation is the content-side twin of snowshoeing: the domain is what a filter scores, so spreading one campaign across several defeats the scoring in exactly the way the numbers version does. The innocent case is per-channel tracking domains, which is a reasonable analytics decision that produces an identical fingerprint.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "domains used across a bulk send",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use one branded domain you control for every link in a campaign, and carry tracking in the path or the query rather than in the hostname.",
      "notes": "Absorbs MSG-109, the same detection stated from the message-content side. Visible only across a real send. What the user has to check is whether their marketing platform issues per-campaign or per-channel domains automatically — several do, and the resulting pattern is indistinguishable from deliberate rotation.",
      "catalogIds": [
        "MSG-109"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your marketing platform issue a different link domain per campaign or per channel?",
        "howToCheck": [
          "Look at the links in two recent campaigns and compare the hostnames. Several platforms rotate automatically, and the pattern is indistinguishable from deliberate evasion.",
          "Use one branded domain you control for every link.",
          "Carry tracking in the path or the query string rather than in the hostname."
        ],
        "failureLooksLike": "Per-channel tracking domains are a reasonable analytics decision that produces exactly the fingerprint of URL rotation — the content-side twin of snowshoeing."
      }
    },
    {
      "id": "OPS-145",
      "slug": "ops-145",
      "title": "Consecutive number ranges are a volume signal",
      "statement": "Consecutive number ranges must not be purchased to support higher-volume sending.",
      "rationale": "A block of sequential numbers is the cheapest way to buy capacity and the most obvious pattern to detect, so it functions as a declaration that the intent was volume rather than presence. It is also snowshoeing with a paper trail, which makes the intent argument harder to have.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "provisioned number contiguity",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Buy numbers as the programme genuinely needs them, and take throughput from vetting and an approved pool rather than from number count. Done when the numbers on a campaign are not a contiguous block.",
      "notes": "Number inventory at the provider. What the user has to check is how their numbers were purchased — a bulk order fulfilled from one range is the common cause and nobody chose it deliberately.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Are the numbers on this campaign a contiguous block?",
        "howToCheck": [
          "List them and look. A bulk order fulfilled from one range is the common cause and nobody chose it deliberately.",
          "Buy numbers as the programme genuinely needs them, and take throughput from vetting and an approved pool rather than from number count."
        ],
        "failureLooksLike": "A sequential block is the cheapest way to buy capacity and the most obvious pattern to detect. It reads as a declaration that the intent was volume rather than presence, which makes the intent argument much harder to have."
      }
    },
    {
      "id": "OPS-146",
      "slug": "ops-146",
      "title": "Identical bodies across a bulk send fingerprint the campaign",
      "statement": "Message body wording should vary across a bulk send rather than being byte-identical to every recipient.",
      "rationale": "Filters fingerprint exact bodies, so a million identical messages are one signature seen a million times and a single spam report can suppress the lot. Varying the wording is not evasion — it is the difference between a campaign that degrades gracefully under a complaint and one that stops entirely.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "body variance across a bulk send",
      "severity": "LOW",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Plivo",
        "Vonage"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Personalise at least one element per message — the recipient name, the item, the store — so bodies differ while the offer does not. Done when no two messages in a send are byte-identical.",
      "example": "Acme Coffee: {{first_name}}, 20% off {{last_bought}} this weekend at {{store}}. Reply STOP to opt out.",
      "pitfalls": [
        "Random character insertion or deliberate misspelling is evasion rather than variance, and it is prohibited outright — vary the content, not the spelling. OPS-072 covers that line."
      ],
      "notes": "A property of a real send. What the user has to check is whether their template has any per-recipient field at all — a template with none produces identical bodies however sophisticated the platform is.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your template contain any per-recipient field at all?",
        "howToCheck": [
          "Open the template and look for a merge field — the recipient name, the item, the store.",
          "A template with none produces byte-identical bodies however sophisticated the platform is.",
          "Vary the content, never the spelling: random characters or deliberate misspellings are evasion and prohibited outright."
        ],
        "failureLooksLike": "A million identical messages are one signature seen a million times, so a single spam report suppresses the lot rather than degrading the campaign gracefully."
      }
    },
    {
      "id": "OPS-149",
      "slug": "ops-149",
      "title": "Live content has to keep matching the registration",
      "statement": "Traffic must continue to match the registered use case and the registered samples, not merely have matched them at approval.",
      "rationale": "The samples are a promise about the programme, and programmes drift: a transactional campaign starts carrying a seasonal offer, an account-notification campaign adds a survey. Nobody re-reads the registration when writing a new template, so the drift is gradual and nothing flags it until the traffic no longer resembles what was approved.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "live traffic against the registered use case and samples",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "T-Mobile",
        "Twilio",
        "Telnyx",
        "Vonage"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Review live templates against the registered samples on a schedule, and register a new campaign — or change the use case — before sending a category the registration does not describe. Done when every template in production would be recognised by somebody reading the registration.",
      "notes": "Absorbs MSG-279, the same duty from the message-content side. Requires the live templates. What the user has to own is the review: the approval-side rules — MSG-141 and OPS-USECASE-SCOPE — check what was submitted, and neither of them sees what was added afterwards.",
      "catalogIds": [
        "MSG-279"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Would somebody reading your registration recognise every template now in production?",
        "howToCheck": [
          "Put the live templates next to the registered samples and read them together, on a schedule.",
          "Register a new campaign — or change the use case — before sending a category the registration does not describe.",
          "Nobody re-reads the registration when writing a new template, so the drift is gradual and nothing flags it."
        ],
        "failureLooksLike": "A transactional campaign starts carrying a seasonal offer; an account-notification campaign adds a survey. The samples were a promise about the programme, and the traffic no longer resembles what was approved."
      }
    },
    {
      "id": "OPS-150",
      "slug": "ops-150",
      "title": "Send only the message types the subscriber agreed to",
      "statement": "Message types the subscriber did not consent to must not be sent — a survey to an OTP-only list, marketing to a transactional opt-in.",
      "rationale": "Consent has a scope, and the scope is the category described at opt-in, not the sending platform's idea of a contact list. Sending outside it is unconsented messaging however carefully the original opt-in was collected, and it is the failure that turns a well-run programme into a complaint source, because the recipients did opt in — to something else.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "message type against the consent record scope",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Store the consent scope on the record and check it before a send rather than segmenting by list membership. Done when a marketing send cannot include numbers whose consent was transactional.",
      "notes": "A comparison between a send and a stored scope. What the user has to check is whether the scope is stored at all — where consent is a boolean, this rule cannot be satisfied by any amount of care at send time. OPS-209 and OPS-199 cover recording it.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is the consent scope stored on each record — or is consent a boolean?",
        "howToCheck": [
          "Look at the consent record itself. Where consent is a boolean, this rule cannot be satisfied by any amount of care at send time.",
          "Check the scope before a send rather than segmenting by list membership.",
          "Done when a marketing send cannot include numbers whose consent was transactional."
        ],
        "failureLooksLike": "A survey to an OTP-only list. The recipients did opt in — to something else — which is what turns a carefully run programme into a complaint source."
      }
    },
    {
      "id": "OPS-151",
      "slug": "ops-151",
      "title": "Separate marketing traffic once volume matters",
      "statement": "At medium-to-high volume, marketing traffic should sit on its own campaign and number set, with a separate short code per brand and per message type.",
      "rationale": "Marketing is what draws complaints and filtering, and mixing it with transactional traffic means a filtering event takes the order confirmations down with the promotions. Separation limits the blast radius: the campaign that gets throttled is the one that earned it, and the messages customers actually need keep arriving.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign and number segmentation by traffic type",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Bird",
        "SignalWire",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register marketing and transactional traffic as separate campaigns on separate numbers once volume justifies the second registration. Done when a filtering event on promotions cannot affect order updates.",
      "notes": "An architecture decision inside the sending account. What the user has to weigh is the cost of a second registration against the cost of transactional traffic being filtered alongside marketing — the second is usually much larger and much later.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could a filtering event on your promotions take your order confirmations down with it?",
        "howToCheck": [
          "Check whether marketing and transactional traffic share a campaign and a number set.",
          "Weigh the cost of a second registration against the cost of transactional traffic being filtered alongside marketing — the second is usually much larger and arrives much later.",
          "Separate them once volume justifies it."
        ],
        "failureLooksLike": "Marketing is what draws complaints and filtering. Mixed traffic means the throttle lands on the order updates customers actually need."
      }
    },
    {
      "id": "OPS-152",
      "slug": "ops-152",
      "title": "One recognisable number per programme",
      "statement": "A business or programme should send from one primary, recognisable source number.",
      "rationale": "Recognition is the consumer's only defence against smishing: somebody who knows the number their pharmacy texts from can tell a fake from a real one. A programme that sends from a rotating set trains its own customers to accept messages from unfamiliar numbers, which is precisely the habit the industry is trying to break.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "programme to source number mapping",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send each programme from one number and publish it, so customers can recognise and save it. Done when a customer could tell you which number your texts come from.",
      "notes": "Sending configuration. What the user has to reconcile is this against any pool they operate — a pool is legitimate for capacity and still argues for one primary number per programme rather than a rotating presentation.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Could a customer tell you which number your texts come from?",
        "howToCheck": [
          "Send each programme from one primary number and publish it so customers can recognise and save it.",
          "Reconcile this against any pool you operate: a pool is legitimate for capacity and still argues for one primary number per programme rather than a rotating presentation."
        ],
        "failureLooksLike": "A programme sending from a rotating set trains its own customers to accept messages from unfamiliar numbers — precisely the habit that makes smishing work."
      }
    },
    {
      "id": "OPS-153",
      "slug": "ops-153",
      "title": "The sender must be who the message says it is",
      "statement": "The message number and sender name must not be spoofed or substituted in any way that misleads a consumer.",
      "rationale": "Sender identity is the only thing a recipient has to judge a message by, and misrepresenting it is the mechanic behind every smishing campaign — which is why the prohibition is absolute rather than proportionate. Legitimate substitution exists, for failover or for a shared brand, and it requires an arrangement with the provider rather than a configuration change.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "outbound sender identity",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "all MNOs"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Send from the numbers registered to the campaign, presented as themselves. Where a substitution is genuinely needed, arrange it with the provider in advance rather than configuring it.",
      "notes": "Wire-level behaviour. What the user has to confirm is that no part of their stack rewrites the originator — some aggregation layers do it for routing reasons, and the sender is accountable for the result either way.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does any part of your stack rewrite the originating number or sender name?",
        "howToCheck": [
          "Ask your provider and check any aggregation layer between your application and the carrier — some rewrite the originator for routing reasons.",
          "Confirm messages arrive from the numbers registered to the campaign, presented as themselves.",
          "Where a substitution is genuinely needed, arrange it with the provider in advance rather than configuring it."
        ],
        "failureLooksLike": "A routing layer substitutes the originator without anybody choosing it. Sender identity is the only thing a recipient can judge a message by, and the sender is accountable for the result either way."
      }
    },
    {
      "id": "OPS-154",
      "slug": "ops-154",
      "title": "Where the numbers came from matters",
      "statement": "Volume must not originate from SIM boxes, SIM farms, or disposable, rental or temporary telephone numbers.",
      "rationale": "These are the origination methods that exist to be untraceable, so their presence is treated as evidence about the sender rather than about the traffic. The industry watches for ten or more activations by one party in a short window and a single location, which means a legitimate business scaling quickly through the wrong supplier can trip a fraud signal it never knew existed.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "originating number provenance",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Provision numbers through a licensed carrier or CPaaS in the brand's own name, and keep the assignment records. Refuse any supplier that cannot say who the numbers are registered to.",
      "notes": "Provenance held by whoever assigned the numbers. What the user has to establish is exactly that — who the numbers are registered to and how they were obtained — before volume makes the question urgent. OPS-021 is the ownership-documentation half.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Who are your sending numbers registered to, and how were they obtained?",
        "howToCheck": [
          "Get the assignment records from whoever provisioned them.",
          "Refuse any supplier that cannot say who the numbers are registered to — that answer is the whole check.",
          "Provision through a licensed carrier or CPaaS in the brand's own name."
        ],
        "failureLooksLike": "Ten or more activations by one party in a short window from a single location is the industry's fraud signature. A legitimate business scaling quickly through the wrong supplier trips it without ever knowing it existed."
      }
    },
    {
      "id": "OPS-155",
      "slug": "ops-155",
      "title": "Sending through a provider you have no relationship with is a red flag",
      "statement": "A message sender with no business relationship with the CPaaS or wireless provider carrying its traffic must be treated as a compromised-system indicator.",
      "rationale": "Every legitimate sender has a contract with somebody in the chain. Traffic arriving without one usually means compromised API credentials or a resold account, and the industry treats it as a compromise signal rather than as an unusual commercial arrangement — so a brand whose traffic reaches a carrier through an intermediary it has never heard of is indistinguishable from a breach.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "the contractual chain behind the traffic",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Know every party in your sending chain and hold a contract with the one you send through. Done when you can name the provider, the aggregator and the terminating carrier for your own traffic.",
      "notes": "A commercial fact outside the registration. What the user has to do is map their own chain — resold accounts and white-labelled platforms make this genuinely unclear, and unclear is the state this rule is about.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Can you name the provider, the aggregator and the terminating carrier for your own traffic — and do you hold a contract with the one you send through?",
        "howToCheck": [
          "Map the chain end to end. Resold accounts and white-labelled platforms make this genuinely unclear, and unclear is the state this rule is about.",
          "Confirm there is a contract with the party you send through."
        ],
        "failureLooksLike": "Traffic reaching a carrier through an intermediary you have never heard of is indistinguishable from compromised API credentials, and it is treated as a compromise signal rather than as an unusual commercial arrangement."
      }
    },
    {
      "id": "OPS-156",
      "slug": "ops-156",
      "title": "Watch for traffic that is being pumped",
      "statement": "Content and traffic patterns consistent with artificially inflated traffic must be detected and rejected.",
      "rationale": "Artificially inflated traffic is fraud committed through your account rather than by you: an attacker drives OTP or verification sends to number ranges that pay them a share of the termination fee, and the bill is yours. The signature is a destination distribution that does not look like your customers — sudden concentration in unusual ranges or countries — and it costs real money for as long as nobody is watching.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "traffic pattern and destination distribution",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rate-limit verification and OTP endpoints per source, alarm on shifts in destination distribution, and cap spend per hour so a pumping attack is bounded. Done when an unexpected concentration of destinations pages somebody within the hour.",
      "pitfalls": [
        "A CAPTCHA on the form does not stop this — the attack drives the API behind it, so the control has to sit at the send."
      ],
      "notes": "A pattern in live traffic. What the user has to do is put a spend cap and a distribution alarm on any endpoint a stranger can trigger, because the exposure is unbounded until one exists.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is there a spend cap and a destination-distribution alarm on every endpoint a stranger can trigger?",
        "howToCheck": [
          "List the endpoints that send without a logged-in user — verification and OTP are the usual ones.",
          "Rate-limit per source, cap spend per hour, and alarm on shifts in destination distribution.",
          "A CAPTCHA on the form does not stop this: the attack drives the API behind it, so the control has to sit at the send."
        ],
        "failureLooksLike": "An attacker drives OTP sends to number ranges that pay them a share of the termination fee. It is fraud committed through your account rather than by you, the bill is yours, and the exposure is unbounded until a cap exists."
      }
    },
    {
      "id": "OPS-157",
      "slug": "ops-157",
      "title": "A provider can block anything its risk assessment dislikes",
      "statement": "Service providers may block any message a reasonable risk assessment suggests is unwanted, and the triggers include compromised credentials, grey routes, missing authentication and a pattern of abusing best practices.",
      "rationale": "This is the discretion behind every other rule in the folder, and it matters because it is not appealable in the way a rejection is: the traffic simply stops. Understanding that the assessment looks at posture rather than at individual messages is what makes the rest of the family worth acting on before anything goes wrong.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "overall sending posture",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "CTIA",
        "all service providers"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Treat the posture as the thing being judged: authenticated sending, one static route, credentials under control, complaint and opt-out rates monitored. Done when nothing about your traffic requires explaining.",
      "notes": "A judgement made by somebody else about traffic we cannot see. What the user has to accept is that there is no threshold to comply with here — the mitigation is the rest of this folder, and the practical step is knowing who to call at the provider before it happens.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Do you know who to call at your provider before traffic is blocked, rather than after?",
        "howToCheck": [
          "Get the name and escalation path now. There is no threshold to comply with here — the assessment looks at posture, not at individual messages, and the traffic simply stops.",
          "Work the posture instead: authenticated sending, one static route, credentials under control, complaint and opt-out rates monitored.",
          "Done when nothing about your traffic requires explaining."
        ],
        "failureLooksLike": "Not appealable in the way a rejection is. Delivery stops, no code explains it, and the conversation starts from scratch with somebody you have never spoken to."
      }
    },
    {
      "id": "OPS-159",
      "slug": "ops-159",
      "title": "A registered campaign must expect to carry traffic",
      "statement": "A campaign should be registered only when it will actually send; sustained inactivity triggers dormancy suspension.",
      "rationale": "Registering ahead of a programme that does not exist yet wastes a non-refundable vetting fee and risks a dormancy suspension after roughly two weeks of silence. It also produces the empty-shell registrations that make reviewers stricter with everyone.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.description",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register when you have subscribers and a send schedule. If the programme is seasonal, plan at least occasional traffic to avoid dormancy suspension.",
      "example": "Weekly promotional sends beginning the week of approval.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-161",
      "slug": "ops-161",
      "title": "Campaign auto-renewal must be set deliberately",
      "statement": "The autoRenewal setting must be chosen deliberately — true renews monthly, false deactivates at cycle end.",
      "rationale": "Left unconsidered, this setting either bills you monthly forever or silently deactivates a live campaign at the end of its cycle, taking your traffic down with no rejection to explain it. It is one of the few settings whose default choice has an ongoing cost either way.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.auto_renewal",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set auto-renewal true for an ongoing programme so it does not deactivate mid-flight, and false only for a genuinely time-boxed campaign you intend to end.",
      "example": "Ongoing marketing programme → autoRenewal: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-180",
      "slug": "ops-180",
      "title": "Approved samples do not license prohibited content",
      "statement": "Prohibited content must not appear in live traffic regardless of what the approved samples showed.",
      "rationale": "Approval is granted against the samples, and operators read it as approval of the campaign — so a programme whose samples were clean starts sending content the category prohibits, on the basis that the campaign was approved. Carriers scan live traffic, and a violation there is enforced against the account rather than the campaign.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "live message content",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Apply the prohibited-content rules to every template before it goes live, not only to the ones submitted for registration. Treat the registered samples as examples of a standard rather than as the extent of it.",
      "notes": "Live content we never see. What the user has to put in place is a review step on new templates — the MESSAGE_CONTENT prohibited-content family states the categories, and this rule is the reminder that approval did not exempt anything from them.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is there a review step that applies the prohibited-content rules to new templates, not just to the ones submitted for registration?",
        "howToCheck": [
          "Find the step. Approval was granted against the samples, and operators read it as approval of the campaign.",
          "Apply the MESSAGE_CONTENT prohibited-content categories to every template before it goes live.",
          "Treat the registered samples as examples of a standard rather than as the extent of it."
        ],
        "failureLooksLike": "A programme whose samples were clean starts sending content the category prohibits, on the basis that the campaign was approved. Carriers scan live traffic and enforce against the account rather than the campaign."
      }
    },
    {
      "id": "OPS-199",
      "slug": "ops-199",
      "title": "Consent records must capture all seven required fields",
      "statement": "The programme must retain a consent record holding the number, timestamp, medium, a capture of the consent experience, the campaign, an IP or call identifier, and the consenting party.",
      "rationale": "CTIA enumerates these seven fields specifically, and the reason is evidentiary: when a complaint arrives eighteen months later, \"opted in: true\" proves nothing about what the consumer actually agreed to. The capture-of-experience field is the one almost everybody omits, and it is the one that matters most — it is the only record of the wording shown at the moment of consent.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Store all seven fields per opt-in, and version the disclosure text so you can reproduce exactly what a given consumer saw. Describe this in the message flow — a reviewer treats an undescribed record-keeping practice as an absent one.",
      "example": "For each opt-in we store: phone number, UTC timestamp, acquisition medium, the versioned disclosure text shown, the campaign, the originating IP (or call ID for phone consent), and the account or session identity that consented.",
      "pitfalls": [
        "Storing a boolean flag with a date is the most common shortfall — it cannot answer \"what did they agree to?\"."
      ],
      "catalogIds": [
        "OPS-202",
        "OPS-203"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-201",
      "slug": "ops-201",
      "title": "The consent experience itself must be versioned and retained",
      "statement": "A capture of the exact language and action used to secure consent must be retained and versioned per consumer.",
      "rationale": "Disclosure wording changes over time, so a single current screenshot cannot evidence a consent given two revisions ago. Versioning is what lets you answer which text a specific consumer saw, and it is the only defence when the wording has since improved.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Version the disclosure text and store the version identifier on each consent record, so any past consent can be replayed exactly as the consumer saw it.",
      "example": "Each opt-in stores disclosure_version=2026-03-a alongside the timestamp; we retain the full text of every version.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-204",
      "slug": "ops-204",
      "title": "Verbal consent records must substitute a call identifier for the IP address",
      "statement": "Where consent was verbal or via IVR, a call identifier must replace the IP-address field in the consent record.",
      "rationale": "The IP field is meaningless for phone consent, and leaving it empty produces a record that looks incomplete rather than method-appropriate. A call ID, recording ID, or agent identifier is the equivalent evidence and is what a reviewer expects for that method.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Store a call ID, recording ID, or agent extension on verbal consent records in place of the IP address, and say so in the message flow.",
      "example": "Verbal opt-ins store the call UCID, the agent ID, and the script version read, in place of an IP address.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-207",
      "slug": "ops-207",
      "title": "The consent record must name the page the opt-in came from",
      "statement": "Every consent record must carry the source URL of the consent surface.",
      "rationale": "A brand usually has several places a consumer can opt in — a footer form, a checkout box, a landing page for one campaign — and they carry different disclosure wording. Storing only \"web\" makes every one of those records identical, so when a reviewer asks which page a specific consumer used, nobody can answer. The innocent cause is that the form posts to one endpoint, and the endpoint records itself rather than the page that called it.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "Telnyx"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email",
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social, email and QR code.",
      "universal": false,
      "remediation": "Store the full URL of the page the opt-in was submitted from — path and query included — on each consent record, and describe that in the message flow. Done when you can take any consent record and open the exact page that produced it.",
      "example": "Each opt-in stores source_url: https://acmecoffee.com/signup?src=instore-qr, alongside the timestamp and IP.",
      "pitfalls": [
        "Storing the form-handler endpoint rather than the referring page produces one URL for every record, which is the same failure with a longer string in it."
      ],
      "notes": "Registration-side twin: we judge whether the programme describes storing the field, never whether the stored record exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-208",
      "slug": "ops-208",
      "title": "The consent record should carry the browser user agent",
      "statement": "Every consent record must carry the device or browser user agent behind the submission.",
      "rationale": "When a consumer says they never filled in the form, the IP and timestamp establish that something was submitted and nothing more. The user agent is the one field on the record that speaks to how it was submitted — a real browser on a phone reads differently from a scripted post — and it is free to capture, since the header arrives with every request.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "mytcrplus"
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "account_settings",
          "third_party",
          "chat",
          "social",
          "email",
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout, account settings, third party, chat, social, email and QR code.",
      "universal": false,
      "remediation": "Capture the User-Agent header alongside the IP address at submission and store it on the consent record. Done when a record shows the browser and device as well as the address it came from.",
      "example": "Each web opt-in stores user_agent: \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_2 like Mac OS X) …\" beside the IP.",
      "notes": "Registration-side twin: we judge whether the programme describes storing the field, never whether the stored record exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-209",
      "slug": "ops-209",
      "title": "The consent record must say which act gave consent",
      "statement": "Every consent record must carry the confirmation method used — a ticked box, a click, or a replied keyword.",
      "rationale": "Two records can agree on number, timestamp and campaign and still describe completely different events: one consumer ticked an unchecked box, another replied YES to a text, a third was added by an agent. Those are not equally strong evidence, and a record that does not say which act happened cannot be graded — which is exactly the moment, months later, when it needs to be.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "mytcrplus"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Store the specific act on each record — checkbox, button click, SMS reply, keypress, agent entry — rather than a boolean. Done when reading one record tells you what the consumer physically did.",
      "example": "Each opt-in stores confirmation_method: checkbox_ticked (or sms_reply_yes for double opt-ins).",
      "notes": "Registration-side twin: we judge whether the programme describes storing the field, never whether the stored record exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-210",
      "slug": "ops-210",
      "title": "Telemarketing consent must retain the request, its purpose and the answer",
      "statement": "Where the Telemarketing Sales Rule applies, the programme must retain the consent request as it was presented, the purpose consent was sought for, and the consent actually given with its date and the consenting party.",
      "rationale": "The TSR is stricter than the carrier framework and is enforced by a different body, so a brand can satisfy every 10DLC requirement and still be exposed. What it adds is the request: the FTC wants the ask preserved in the same form the consumer met it, because a permissive record of the answer says nothing about what the question was. The rule also settles a common shortcut explicitly — an IP address plus a timestamp is not a record of consent.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Retain three things per consumer: a copy of the consent request in the format it was presented, the purpose it was sought for, and the consent given with its date, the consenting person and their number. Done when a record answers \"what were they asked, what for, and what did they say\" without anybody reconstructing it.",
      "example": "Each marketing opt-in stores: the rendered disclosure as shown (disclosure_version 2026-03-a), purpose=\"recurring promotional offers from Acme Coffee\", and the consent event — 2026-03-04, Jane Doe, +1 415-555-0134, checkbox ticked.",
      "pitfalls": [
        "Storing the current disclosure text rather than the version shown at the time satisfies neither half — the request must be the one that consumer met."
      ],
      "notes": "Absorbs OPS-211 (purpose), OPS-212 (the consent, date, name and number) and OPS-213, which states the sufficiency test rather than a fourth field. Tagged to the marketing-style use cases because the TSR reaches telemarketing; a transactional programme outside its scope is not failed for the absence.",
      "catalogIds": [
        "OPS-211",
        "OPS-212",
        "OPS-213"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-214",
      "slug": "ops-214",
      "title": "Telemarketing consent taken by phone must retain the recording of both halves",
      "statement": "Where the Telemarketing Sales Rule applies and consent was spoken, a recording of both the request and the consent given must be retained, making the purpose clear.",
      "rationale": "A recording that starts at \"do you agree?\" preserves the answer and loses the question, which is the half the FTC actually cares about — it is where the purpose is stated and where a leading or truncated ask would show. Contact centres cut it this way because the recording is triggered by the consent step rather than covering the disclosure that precedes it.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FTC"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ],
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR, and the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Record the disclosure and the answer as one continuous recording, and retain it against the consumer record. Done when playing a single recording gives you the purpose, the ask and the agreement without a second file.",
      "example": "Verbal marketing opt-ins retain one recording per consent covering the full disclosure and the consumer's answer, indexed by call UCID.",
      "pitfalls": [
        "Starting the recording at the consent question is the common configuration and it is the failure: the purpose is stated in the sentence before it."
      ],
      "notes": "Where recording is not possible — the consumer declines, or an all-party-consent state applies — OPS-239 describes the fallback the programme must have instead. This rule is about what is retained, not about whether a recording satisfies the written-consent standard, which is CON-156.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-215",
      "slug": "ops-215",
      "title": "Express written consent must name the specific number and bear a signature",
      "statement": "The retained consent agreement must identify the specific telephone number consented for and bear the consumer's signature.",
      "rationale": "TCPA express written consent is a signed agreement naming the number, and an electronic signature under E-SIGN qualifies — a ticked box with a stored identity and timestamp satisfies it. What does not satisfy it is a record that cannot tie a signature to a specific number.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "Ensure the consent record ties an identifiable signature — including an E-SIGN electronic one — to the specific number, and describe that linkage in the message flow.",
      "example": "The checkbox action, the entered phone number, the consenting account identity and the timestamp are stored together as the signed agreement.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-217",
      "slug": "ops-217",
      "title": "Consent must be tracked per consumer, not per list",
      "statement": "Consent must be recorded against the individual consumer rather than at list level.",
      "rationale": "List-level consent cannot answer whether a specific person agreed, which is the only question that matters in a complaint. It is also structurally how purchased lists get laundered into a programme — a list marked \"consented\" with no per-number provenance.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Record consent per phone number with its own timestamp and source, never as a property of a list or segment.",
      "example": "Each number carries its own consent timestamp, medium and disclosure version.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-219",
      "slug": "ops-219",
      "title": "Opt-out records must be retained alongside opt-ins",
      "statement": "Revocation requests must be retained for the same period as opt-ins, with the channel recorded.",
      "rationale": "Proving someone opted in is only half the defence — the other half is proving you stopped when they asked, and when. Revocations arrive by text, call and email, so the channel matters: a revocation taken by a phone agent must reach the same suppression list as a texted STOP.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Retain revocations for the same period as consents, record the channel each arrived on, and route revocations captured by agents or IVR into the same suppression list as texted opt-outs.",
      "example": "Opt-outs are stored with the number, timestamp, and channel (SMS, phone, email), retained for four years alongside consent records.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-221",
      "slug": "ops-221",
      "title": "The retention period must clear the longest window that applies",
      "statement": "The stated retention period for consent and opt-out records must be the maximum of every applicable window, not the first one the brand found.",
      "rationale": "Five separate floors sit under this, set by five different bodies: the telemarketing rule's five years, the four-year TCPA limitation period, the five-year internal do-not-call floor, the short-code handbook's opt-in-until-six-months-after-opt-out, and any longer state rule — Virginia sets ten years for suppression records. They are not alternatives. A brand that reads one source and retains for the period it names is under-retained against the others, and finds out when a claim arrives in year four against records deleted in year two.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "FCC",
        "CTIA",
        "state law"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set one retention period for consent and opt-out records at the longest window that reaches you, and state that period rather than a rule reference. Five years clears the telemarketing rule, the TCPA limitation period and the short-code floor together; check whether a state you message into is longer. Done when the stated number is a single figure you can defend against every source above.",
      "example": "We retain consent and opt-out records for five years from the last message sent in reliance on that consent.",
      "pitfalls": [
        "Citing an authority instead of a number (\"we retain records as required by law\") leaves the period undefined, and an undefined period is administered by whichever system deletes first."
      ],
      "notes": "Absorbs OPS-222 (four-year TCPA), OPS-059 (the five-year internal do-not-call floor), OPS-224 (short-code opt-in and opt-out floors), OPS-225 (the resolution rule itself), and the state rows OPS-060 and OPS-226. They are inputs to one resolver, and implementing any one alone under-retains — which is why they are one rule rather than six. Only the retention half of OPS-059 and OPS-060 is settled here; honouring a suppression record is runtime behaviour and is not claimed. The state check needs a jurisdiction we do not hold, so the criteria ask the model to report it as a residual rather than settle it.",
      "catalogIds": [
        "OPS-059",
        "OPS-060",
        "OPS-222",
        "OPS-224",
        "OPS-225",
        "OPS-226"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-223",
      "slug": "ops-223",
      "title": "The retention clock starts at the last message, not the opt-in",
      "statement": "The retention period must be measured from the last message sent in reliance on the consent, not from the date the consent was collected.",
      "rationale": "A limitation period runs from the act complained of, which is a message, not a signup. Anchoring at collection shortens every window by the life of the subscription: a consumer who opted in five years ago and was messaged last month has a record that a collection-anchored policy deleted years before the message that will be complained about. It is a one-line difference in a retention policy and almost nobody writes it deliberately.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "PossibleNOW"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the anchor into the retention sentence: measure from the last message sent in reliance on that consent, and re-arm the clock on every send. Done when a long-standing subscriber's record survives as long after their most recent message as a new subscriber's does after their first.",
      "example": "Consent records are retained for five years from the last message sent in reliance on that consent.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-232",
      "slug": "ops-232",
      "title": "A spoken opt-in needs a per-consumer artifact, not just a script",
      "statement": "Verbal consent must be evidenced per consumer by a recording, a transcript, or documented contemporaneous notes — never by the script alone.",
      "rationale": "The script proves what the programme intends to say. It is silent about any particular call, so a brand holding only a script has one document and no evidence, and finds that out when a single complaint asks about a single consumer. The transcript or the note is the substitute a reviewer accepts when a recording does not exist, which is why the rule names three acceptable forms rather than demanding audio.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Name in the message flow which artifact is kept per consumer — recording, transcript, or a contemporaneous CRM note carrying the verbatim assent — and keep it against the number. Done when the flow says what exists for one named consumer, not what the agents are told to say.",
      "example": "Each phone opt-in retains the call recording; where the consumer declines recording, the agent stores the verbatim assent and the script version in the CRM against the number.",
      "notes": "Absorbs OPS-234, which states the transcript fallback inside the same record requirement. Whether a recording can stand as WRITTEN consent for a marketing programme is a different question and is judged by CON-156; this rule asks only whether any per-consumer artifact exists.",
      "catalogIds": [
        "OPS-234"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-233",
      "slug": "ops-233",
      "title": "A spoken consent record must name the agent who took it",
      "statement": "Every consent record must carry the identifier of the agent who read the consent script.",
      "rationale": "A verbal opt-in has no artifact the consumer touched, so the whole evidentiary weight sits on the call. Naming the agent is what makes the record investigable: it is how a brand finds out that one agent has been paraphrasing the script, and how it shows a regulator that it looked. Contact centres omit it because the CRM records the account owner rather than whoever was on the call.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal.",
      "universal": false,
      "remediation": "Store the agent identifier — the login, extension or badge number of the person who read the script — on each verbal consent record. Done when any consent record names a specific person, not a team or a queue.",
      "example": "Verbal opt-ins store agent_id: acme-cs-0142 with the call UCID and the script version read.",
      "notes": "Registration-side twin: we judge whether the programme describes storing the field, never whether the stored record exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-235",
      "slug": "ops-235",
      "title": "A spoken consent record must pin the script version that was read",
      "statement": "Every consent record must carry the version and effective date of the consent script read to the consumer.",
      "rationale": "Scripts get improved, and every improvement makes the previous wording unprovable unless the record says which one was in force. This is the verbal twin of the capture-of-experience field, and it is the difference between \"our script says X\" — which a regulator reads as what you say today — and \"this consumer heard version 3, effective 12 January\". Brands miss it because the script lives in a document nobody versions.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "CTIA",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal, IVR and point of sale.",
      "universal": false,
      "remediation": "Version the consent script, give each version an effective date, and store the version identifier on every consent taken while it was live. Done when you can pick any past consent and reproduce the exact words that consumer heard.",
      "example": "Verbal opt-ins store script_version: 2026-03-a (effective 2026-03-01) with the agent ID and call UCID.",
      "pitfalls": [
        "Attaching the current script to the registration proves what is read today. It is silent about the consents already taken, which are the ones a complaint will be about."
      ],
      "notes": "Registration-side twin: we judge whether the programme describes storing the field, never whether the stored record exists.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-236",
      "slug": "ops-236",
      "title": "An IVR opt-in must log the prompt, the keypress, the time and the call",
      "statement": "Every consent record must carry the prompt version played, the digit or spoken answer captured, the timestamp and the call identifier.",
      "rationale": "An IVR consent leaves no human witness on either side, so the log is the entire record — and unlike a live agent, an IVR can be misconfigured to accept any keypress as agreement and will do it identically to everyone who calls. All four fields are needed together: the prompt says what was asked, the captured input says what was answered, and the call identifier is what ties the pair to a person.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "CTIA",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by IVR.",
      "universal": false,
      "remediation": "Log all four values on every IVR consent: the prompt version played, the DTMF digit or recognised phrase captured, the timestamp, and the call identifier. Done when one log line lets you replay what was asked and what was pressed.",
      "example": "IVR opt-ins log: prompt_version=ivr-optin-v4, captured_input=DTMF 1, captured_at=2026-03-04T14:22:11Z, call_id=UCID-88213004.",
      "notes": "Registration-side twin of a runtime log. We judge the description of the log, never the log. The four elements are one record judged in one pass rather than four rules, because they fail together: a log missing any one of them cannot reconstruct a single consent.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-239",
      "slug": "ops-239",
      "title": "Declining to be recorded must not block the opt-in",
      "statement": "A programme taking spoken consent must have a documented no-recording evidence path, and must never refuse a consumer the opt-in because they declined recording.",
      "rationale": "Recording is not always available — the consumer says no, or the call crosses into an all-party-consent state — and a contact centre with no fallback does one of two harmful things: it drops the opt-in the consumer asked for, or it takes it with no evidence at all. Both are avoidable with an attestation that carries the agent, the timestamp and the script version, which is what reviewers accept in place of audio.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal.",
      "universal": false,
      "remediation": "Describe the fallback: a contemporaneous CRM attestation carrying the agent ID, the timestamp, the script version and the consumer's verbatim assent. Done when the flow says what happens when a consumer declines recording, and the answer is not \"we cannot enrol them\".",
      "example": "If the consumer declines recording, the agent stores the verbatim assent, agent ID, timestamp and script version in the CRM; the opt-in proceeds on that record.",
      "pitfalls": [
        "A fallback that only records \"consent given: yes\" is the same evidentiary gap the recording was there to close."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-240",
      "slug": "ops-240",
      "title": "Recordings must outlive the consent records they evidence",
      "statement": "Call recordings must be retained at least as long as the consent records they are the evidence for.",
      "rationale": "Recording retention is usually set by the telephony platform for quality-monitoring reasons — thirty or ninety days is a common default — while consent records are kept for years. The result is a consent record that points at a recording which was deleted long before anyone asked for it, and nobody notices because both systems are behaving exactly as configured.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FTC",
        "CTIA",
        "practice"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Raise the recording retention on the telephony platform to match the consent retention window, or export the consent calls into the consent store. Done when the recording retention setting is at least as long as the period stated for consent records.",
      "example": "Consent-call recordings are retained for four years, matching the consent-record retention period.",
      "pitfalls": [
        "A blanket increase across every call is expensive and usually unnecessary — tag the consent calls and retain those, rather than the whole queue."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-251",
      "slug": "ops-251",
      "title": "Consent left unused for a month must be reconfirmed",
      "statement": "Where no message is sent within 30 days of collecting consent, the programme must reconfirm by double opt-in before its first send.",
      "rationale": "A consumer who signed up and heard nothing for a month has usually forgotten, changed number, or moved on, and the first message they eventually get reads as unsolicited — which is how it gets reported. The 30-day rule exists because that first message is also the one most likely to arrive at a reassigned number. Programmes hit it by launching a signup form months before the messaging goes live, which is the ordinary way to build a list and the exact shape the rule is about.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the reconfirmation: numbers with no message inside 30 days of opt-in receive a single confirmation asking them to reply YES, and are not messaged further until they do. Done when the flow says what happens to a signup that goes unmessaged for a month.",
      "example": "Numbers not messaged within 30 days of opt-in are sent one reconfirmation (\"Reply YES to confirm you still want Acme Coffee texts\") and receive nothing further unless they reply.",
      "pitfalls": [
        "Sending the delayed first message with an opt-out line attached is not reconfirmation — the consumer is being messaged on stale consent, and the opt-out arrives after the message they did not expect."
      ],
      "notes": "Registration-side twin: whether the 30-day clock is honoured is runtime behaviour. What is decidable now is whether the programme has a rule for the case, which matters most for a list built before launch.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-252",
      "slug": "ops-252",
      "title": "Consent collected away from the handset should be confirmed on it",
      "statement": "Where consent is collected outside the SMS channel — web, phone, point of sale or paper — the programme should send a double opt-in confirmation before recurring messages.",
      "rationale": "Off-channel opt-ins are the ones where the number can be wrong: mistyped on a form, misheard on a call, or written illegibly on a card. A confirmation text is the only step that proves the number reached the person who agreed, and it converts a typo from a stranger receiving your marketing into a message nobody answers. It is also what a carrier looks for when a programme built from paper forms starts generating complaints.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + campaign.optin_message",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicability": {
        "excludeConsentMethods": [
          "keyword"
        ]
      },
      "applicabilityText": "Applies when consent was NOT collected by text-to-join keyword.",
      "universal": false,
      "remediation": "Send one confirmation to the number as collected, asking for a reply to activate, and enrol only on that reply. Where a full double opt-in is not workable, at minimum send a single confirmation that names the brand and how to stop before any other message. Done when the flow says what the number receives first.",
      "example": "After a checkout opt-in we text: \"Acme Coffee: reply YES to confirm you want offers and order updates. Msg&data rates may apply. Reply STOP to cancel.\" Numbers are enrolled only on YES.",
      "notes": "Not tagged away from paper, point-of-sale or verbal methods — those are the cases it exists for. It is excluded only where consent arrived on the handset itself, as an SMS keyword, since the number is already proven there.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-256",
      "slug": "ops-256",
      "title": "A recurring programme should expire consent that has gone quiet",
      "statement": "A recurring programme should describe expiring an opt-in after a long period of subscriber inactivity, with one final notification permitted.",
      "rationale": "A subscriber list that only grows eventually consists mostly of numbers that have changed hands, and every one of those is an unconsented message waiting to be sent. Expiry is the cheap version of the reassigned-number problem: it removes the numbers nobody has heard from, which is the same population, without a database lookup. Brands resist it because it shrinks a number they report on.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State an inactivity window after which an opt-in expires — eighteen months is the published figure — and describe the single final message that may be sent before removal. Done when the flow says what happens to a subscriber who never engages.",
      "example": "Subscribers with no engagement for 18 months receive one final notice and are then removed from the programme.",
      "notes": "Registration-side twin: the expiry job runs long after approval. What we judge is whether the programme has a policy for it, which is the part a reviewer can also see.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-261",
      "slug": "ops-261",
      "title": "Only the charity may use the numbers a charity collects",
      "statement": "A charitable programme must let no entity other than the charity use the collected subscriber data, and only for the campaign it was collected for.",
      "rationale": "Charitable messaging gets favourable treatment because donors are giving to a cause, not entering a marketing relationship — and the data is unusually valuable precisely because it identifies people who give. The restriction is absolute for that reason: no agency, no fundraising partner, no affiliated foundation. It is broken innocently when the campaign is run by an agency whose own platform holds the list.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "T-Mobile",
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "State that subscriber data collected by the programme is used only by the charity and only for this campaign, and that no partner, agency or affiliate uses any part of it. Where an agency operates the sending, say it processes the data for the charity and holds no independent right to it. Done when the flow and the policy agree that the charity is the only user.",
      "example": "Numbers collected by Riverside Shelter's text programme are used only by Riverside Shelter and only for this campaign; our messaging agency processes them on our behalf and may not use them for anything else.",
      "pitfalls": [
        "Naming an agency as a joint controller, or a parent foundation as a permitted recipient, breaks the restriction even where both are non-profits."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-292",
      "slug": "ops-292",
      "title": "Evidence screenshots must say when and where they were taken",
      "statement": "Every submitted evidence image must carry its capture timestamp, the source URL, and enough provenance to tie it to the live surface.",
      "rationale": "A screenshot with no provenance is a picture of a form, and a reviewer cannot tell whether it is the live page, a staging build, or a mock-up made for the submission — so the cautious ones reject it and the rest approve something they did not verify. Adding the URL and the date costs one line in the upload and turns the image into evidence, which is the difference the brand is actually being asked for.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.consent_artifact[]",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "CTIA",
        "researcher recommendation"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Capture the browser address bar in the screenshot, or caption each image with the full URL and the capture date where the surface is not a web page. Done when the image alone tells a reviewer which page it is and when it was taken.",
      "example": "Screenshot filename optin-signup-2026-03-04.png, captioned \"https://acmecoffee.com/signup — captured 4 March 2026, 1440×900\", with the address bar visible in the image.",
      "pitfalls": [
        "Cropping to the consent block removes the address bar, which is usually the only provenance the image had."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-323",
      "slug": "ops-323",
      "title": "An identity appeal has a window, and it runs from brand creation",
      "statement": "An identity-status appeal must be submitted within 45 calendar days of the brand being created, not of the decision being received.",
      "rationale": "The clock is the surprising part: it starts when the brand record is created, so a registration that sat in review for three weeks and was refused has already spent most of its appeal window before anybody saw the refusal. Teams plan the appeal around the rejection date, gather the CP-575, and file after the window shut — at which point the only route left is a new brand and a second set of fees.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "brand appeal submission vs brand creation date",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Diarise the appeal deadline as 45 days from the day the brand was created, and gather the federal tax documentation while the review is still running rather than after a refusal. Done when the appeal is filed inside that window, with every page of the CP-575 or 147C attached.",
      "notes": "Not decidable here: no submitted field carries the brand creation date, so the registry cannot compute the deadline. Surfaced as a warning with the check the user has to run themselves — look up the brand's creation date in the registry console, add 45 days, and compare that to today before planning any appeal. BRD-260 and BRD-258 cover what the appeal must contain once you are inside the window.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "What date was this brand created, and is today inside the 45 days that run from it?",
        "howToCheck": [
          "Look the brand's creation date up in the registry console — not the date of the refusal, which is what everyone plans around.",
          "Add 45 calendar days and compare that to today.",
          "Gather the federal tax documentation while the review is still running, so an appeal can be filed the day a refusal lands."
        ],
        "failureLooksLike": "A registration sat in review for three weeks and was refused, so most of the appeal window was spent before anyone saw the decision. Filing late leaves only a new brand and a second set of fees."
      }
    },
    {
      "id": "OPS-324",
      "slug": "ops-324",
      "title": "An appeal is void if you edit the identity you are appealing",
      "statement": "Legal name, entity type, tax ID and tax ID issuing country must not be updated before an appeal is filed, and no brand update may be submitted while one is in flight.",
      "rationale": "An appeal argues that the registry got the existing record wrong, so editing that record first removes the thing being argued about — the registry treats the brand as a fresh claim and refuses the appeal, and the appeal window keeps running. The instinct after a rejection is to fix the data and appeal in the same sitting, which is the exact order that destroys the appeal: resubmitting corrected data and appealing an unchanged record are two different remedies and you get to use one.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "brand.appeal + brand.brand_changes",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Decide first which remedy applies: resubmit when the data was wrong, appeal when the data is right and the decision is not. If you are appealing, leave legal name, entity type, tax ID and issuing country exactly as submitted until the appeal completes. Done when the appeal is filed against a record that has not been touched since the decision.",
      "example": "Appealing: brandChanges is empty — the record stays exactly as the registry refused it.",
      "pitfalls": [
        "The lock covers the appeal window as well as the moment before it: an update sent while the appeal is open is refused, and support cannot reopen it for you."
      ],
      "notes": "Absorbs OPS-325, which states the during-appeal half of the same lock; the fix is identical for both. Distinct from BRD-044, which reports what an identity edit costs on any brand — this rule reports what it destroys on an appealing one, and only fires when an appeal is part of the submission.",
      "catalogIds": [
        "OPS-325"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-344",
      "slug": "ops-344",
      "title": "A brand carrying live work cannot have its identity edited at all",
      "statement": "Legal name, entity type, tax ID and tax ID issuing country cannot be edited while the brand has an active campaign or a vet in progress.",
      "rationale": "On a bare brand these four fields are expensive to change; on a brand with a campaign or a vet behind it they are not changeable at all, and the API refuses rather than queues. The reason is that both the campaign and the vet were granted against the identity as it stood, so changing it would leave live traffic authorised by a record that no longer exists. Teams meet this while correcting a typo that has been in the legal name since day one, and the refusal does not say which of the two blockers is in the way.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "brand.brand_changes + brand.active_campaign_count + brand.vetting",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Clear the blocker before the edit: deactivate the campaigns on the brand, or wait for the vet to reach a final state, then make the identity change and expect the brand to re-verify. Where the campaigns must stay live, register the corrected identity as a new brand and migrate rather than editing this one. Done when the brand has no active campaign and no vet in flight at the moment the edit is sent.",
      "example": "Correcting the legal name: deactivate the one live campaign first, edit, re-verify, then re-register the campaign.",
      "notes": "BRD-044 reports what an identity edit costs on any brand — the fee, the reset, the expired vet. This rule reports the case where it is refused outright, which is a different answer and a different fix, so it fires only when a campaign or a vet is actually in the way.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-384",
      "slug": "ops-384",
      "title": "Nothing goes out before the opt-in",
      "statement": "Messages may be sent only after the consumer has opted in; the single opt-out acknowledgement is the only permitted exception.",
      "rationale": "This is the obligation the whole framework exists to enforce, and it is worth stating separately because the failure is rarely a decision to send unsolicited messages — it is a list imported without provenance, a form that wrote consent before the box was ticked, or a test send to a colleague's number that went to the whole file. The consequence does not scale with the intent.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "send-time consent check",
      "severity": "BLOCKING",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "CTIA",
        "FCC",
        "all MNOs"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check consent at send time against a per-number record, never against list membership, and make an unconsented number impossible to include rather than unlikely.",
      "notes": "The runtime form of everything the CONSENT_FLOW layer checks at registration. What the user has to verify is that their send path reads a consent record rather than a segment — segments are built by people and consent records are not.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Does your send path check a per-number consent record, or does it check list membership?",
        "howToCheck": [
          "Trace one send to the thing it actually reads. Segments are built by people; consent records are not.",
          "Make an unconsented number impossible to include rather than unlikely.",
          "The single opt-out acknowledgement is the only message permitted without a prior opt-in."
        ],
        "failureLooksLike": "A list imported without provenance, a form that wrote consent before the box was ticked, or a test send to a colleague that went to the whole file. The consequence does not scale with the intent."
      }
    },
    {
      "id": "OPS-385",
      "slug": "ops-385",
      "title": "A referral programme must not pay people to forward messages",
      "statement": "Consumers who send viral or referral messages must receive nothing of value for doing so.",
      "rationale": "Paying for forwards turns the sender's friends into a purchased audience: the recipient gets a message from someone they trust, sent for a reward rather than because it was relevant, and there is no consent anywhere in the chain. It is the one part of referral marketing the industry prohibits outright, and it is easy to walk into — a discount for a successful invite is exactly the mechanic every growth playbook recommends.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.description + campaign.message_flow + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the incentive from the forwarding step. Reward the new subscriber's own opt-in if you want a referral mechanic at all, and have the invitee opt in on your own surface rather than receiving a message the referrer triggered. Done when nothing of value depends on a consumer sending a message to somebody else.",
      "example": "Share Acme Coffee with a friend — when they sign up at acmecoffee.com/join they get 20% off their first order. (The reward goes to the new customer, not to the referrer.)",
      "pitfalls": [
        "Sweepstake entries, loyalty points and account credit all count as something of value; the prohibition is not limited to cash."
      ],
      "notes": "Applies only to viral and referral programmes, which the registration has no attribute for. The judge is told to PASS immediately where the campaign describes no forwarding mechanic, and this note records the carve-out the applicability model cannot express.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-FREQ-COHERENT",
      "slug": "ops-freq-coherent",
      "title": "Declared frequency must be consistent everywhere it appears",
      "statement": "The message frequency stated in the consent disclosure, the SMS terms, and the campaign description must agree.",
      "rationale": "Frequency appears in at least three places, and each is written at a different time by a different person. A disclosure promising \"up to 4 per month\" beside terms saying \"frequency varies\" beside a description mentioning daily deals gives a reviewer three different programmes to choose between, and they choose the one that fails.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "consent disclosure + terms + campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick one frequency statement and use it verbatim in all three places. \"Message frequency varies\" is the safest when volume is irregular, because a numeric cap becomes a promise you are held to.",
      "example": "All three read: \"Message frequency varies.\"",
      "catalogIds": [
        "CON-104"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-HELP-ONE-TIME",
      "slug": "ops-help-one-time",
      "title": "HELP must be supported even on one-time programmes",
      "statement": "HELP keyword support is required regardless of whether the programme sends recurring messages.",
      "rationale": "CTIA requires HELP to produce a response whether or not the requester is subscribed and whether or not the programme is a subscription. One-time programmes routinely omit it on the reasoning that there is nothing to unsubscribe from, which leaves a recipient unable to identify the sender at all.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.help_keywords",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare HELP support and configure a reply, even for a single-message programme. The reply must name the brand and give a support route.",
      "example": "Help keywords: HELP, INFO",
      "catalogIds": [
        "OPS-036"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-OPTOUT-CONF-ONLY",
      "slug": "ops-optout-conf-only",
      "title": "Opt-out confirmation must be the only message sent after revocation",
      "statement": "The campaign must declare a single opt-out confirmation and nothing further after a consumer revokes.",
      "rationale": "One final confirmation is permitted; anything after it is messaging someone who has revoked consent, which is both the clearest TCPA violation available and a carrier fee event. Declaring a confirmation is fine — declaring a follow-up sequence is a self-reported violation.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.optout_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Configure exactly one opt-out confirmation, keep it non-promotional, and make sure no other automation can fire at a revoked number afterwards.",
      "example": "Acme Coffee: you have been unsubscribed and will receive no further messages. Reply HELP for help.",
      "catalogIds": [
        "OPS-048"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-POOL-DECLARED",
      "slug": "ops-pool-declared",
      "title": "Number pooling must be declared consistently with the campaign scale",
      "statement": "The number-pool attribute must reflect whether the campaign will actually send from multiple numbers.",
      "rationale": "Number pooling is a filtering signal: sending similar content across many numbers without declaring a pool is the definition of snowshoeing, which carries a four-figure pass-through fee. Declaring a pool you do not use is harmless by comparison, so the asymmetry favours declaring.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.number_pool",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the number-pool attribute true if the campaign will send from more than one number. Never spread similar content across undeclared numbers — that is snowshoeing and it is fined rather than rejected.",
      "example": "Sending from 5 numbers → number pool: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-QUIET-HOURS-DISCLOSED",
      "slug": "ops-quiet-hours-disclosed",
      "title": "Quiet-hours handling should be disclosed for promotional programmes",
      "statement": "A promotional campaign should indicate that sending respects recipient-local quiet hours.",
      "rationale": "Several US state mini-TCPAs impose quiet-hours windows measured in the recipient's local time, and violations are litigated rather than merely filtered. A national campaign that never mentions time-of-day handling is the profile plaintiffs' firms look for, so disclosing it is cheap insurance.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.description + campaign.message_flow",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "FCC",
        "state law"
      ],
      "applicability": {
        "useCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE",
          "LOW_VOLUME"
        ]
      },
      "applicabilityText": "Applies when the use case is MARKETING, MIXED, SWEEPSTAKE and LOW_VOLUME.",
      "universal": false,
      "remediation": "State that promotional sends are restricted to daytime hours in the recipient's local time zone, and configure your platform to enforce it per-recipient rather than per-server.",
      "example": "Promotional messages are sent between 9am and 8pm in the recipient's local time zone, and are suppressed in states with stricter windows.",
      "notes": "Registration-side twin — we verify the disclosure, not the send-time behaviour.",
      "catalogIds": [
        "OPS-091"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-RETENTION-STATED",
      "slug": "ops-retention-stated",
      "title": "Consent record retention period should be stated",
      "statement": "The programme should state how long consent and opt-out records are retained.",
      "rationale": "Retention is what lets you answer a complaint eighteen months later, and CTIA sets a floor of six months after opt-out while TCPA exposure runs to four years. A programme that never says how long it keeps records usually has not decided, which is the same as not retaining.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.message_flow + privacy policy",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State a retention period covering both consent and opt-out records. Four years matches the TCPA statute of limitations and comfortably exceeds the CTIA floor.",
      "example": "We retain consent and opt-out records for four years from the date of collection or revocation.",
      "catalogIds": [
        "POL-102",
        "POL-103"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-USECASE-SCOPE",
      "slug": "ops-usecase-scope",
      "title": "The campaign must not describe content it is not registered to send",
      "statement": "The description must not mention message categories outside the declared use case.",
      "rationale": "Live traffic must match the registered use case, and the description is where a mismatch is visible before it becomes a filtering problem. A description promising \"and occasional special offers\" under a transactional use case has pre-announced the violation.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.description + campaign.usecase",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove out-of-scope categories from the description, or change the use case to MIXED and collect promotional-grade consent to cover them.",
      "example": "ACCOUNT_NOTIFICATION description should describe only account and order messaging, with no promotional aside.",
      "catalogIds": [
        "CMP-039"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "OPS-VOLUME-PLAUSIBLE",
      "slug": "ops-volume-plausible",
      "title": "Declared volume must be plausible for the business",
      "statement": "The message volume the campaign implies should be consistent with the size of the business described.",
      "rationale": "A two-location coffee shop declaring a million messages a month is either mis-declaring or fronting for someone else's traffic, and volume mismatch is a signal reviewers use to detect resold registrations. Under-declaring is also a problem, since throughput is granted against the declaration.",
      "layer": "OPERATIONAL",
      "layerSlug": "operational",
      "object": "campaign.description + brand website",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "T-Mobile",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare volume consistent with your actual subscriber base. If you expect rapid growth, register at current scale and request an increase later rather than over-declaring now.",
      "example": "A single-location retailer with ~5,000 subscribers should not declare enterprise-tier volume.",
      "catalogIds": [
        "CMP-055"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-001",
      "slug": "pol-001",
      "title": "A privacy policy URL is required on the campaign",
      "statement": "Every campaign registration must carry a non-empty privacy policy URL.",
      "rationale": "The policy is the document a consumer is entitled to read before handing over a number, so a registration without one is asking carriers to approve a programme nobody can inspect. Twilio codes the missing field separately from a failed policy review precisely because the usual cause is mechanical — integration code written before the field became mandatory simply never sends it, and every campaign that integration creates fails the same way.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "campaign.privacyPolicyUrl",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS",
        "Telnyx",
        "Sinch",
        "Plivo",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30933",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the privacy policy URL on the campaign to the https address of the published policy page. Done when the field holds a URL you can open in a private window and read.",
      "example": "privacy_policy_url: https://acmecoffee.com/privacy",
      "pitfalls": [
        "If campaigns are created through an API integration, the empty field is in your code rather than on the form — check the request body before re-filing, or the next campaign fails identically."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-002",
      "slug": "pol-002",
      "title": "A terms and conditions URL is required on the campaign",
      "statement": "Every campaign registration must carry a non-empty terms and conditions URL.",
      "rationale": "The terms are where the messaging programme is documented for the person receiving it — what it sends, how often, how to leave — so an empty field leaves the consumer with nothing to consult and the reviewer with nothing to check the samples against. TCR calls the field optional while its own guide says compliant terms are required, which is how a registration ends up submitted without one and rejected for not having one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "campaign.termsOfServiceUrl",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS",
        "Sinch",
        "AT&T",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30934",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Twilio",
          "code": "30882",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the terms URL on the campaign to the https address of the SMS terms page. Done when the field holds a URL that opens the messaging terms rather than the general site terms.",
      "example": "terms_and_conditions_url: https://acmecoffee.com/sms-terms",
      "notes": "TCR marks the field optional in its schema and requires compliant terms in its CSP guide. Treated as required here, per the strict-superset model: supplying it costs nothing at the destinations that do not demand it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-003",
      "slug": "pol-003",
      "title": "An uploaded policy document must fit the provider file constraints",
      "statement": "Where a policy is uploaded rather than linked, the file must be a PDF, PNG or JPEG within the tightest provider size cap.",
      "rationale": "Uploading is the sanctioned route for a brand with no website or an offline opt-in, and the caps differ by an order of magnitude — 500 KB at AWS against 10 MB at TCR — so a file that uploads cleanly at one destination is rejected at another with an error about the campaign rather than about the file. A scanned policy from an office printer is several megabytes without anyone intending it to be.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "campaign policy document upload",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS",
        "TCR",
        "Bandwidth",
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Re-export the document as a PDF under 500 KB — print to PDF rather than scanning, or downsample the scan to 150 dpi — and re-upload. Done when the file is one of PDF, PNG or JPEG and under half a megabyte.",
      "example": "acme-privacy-policy.pdf · application/pdf · 210 KB",
      "pitfalls": [
        "A hosted URL is preferred wherever a website exists; WEB-054 fails a PDF that stands in for a page the brand could have published."
      ],
      "notes": "Applies only where a document was uploaded, which no applicability dimension expresses — the check passes immediately when the registration carries none. The 500 KB ceiling is AWS's, taken as the tightest cap so a submission survives a destination that has not been chosen yet.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-005",
      "slug": "pol-005",
      "title": "The terms and conditions attestation must be set",
      "statement": "The campaign's terms-and-conditions attestation flag must be set to true, alongside supplying the URL.",
      "rationale": "The flag is a declaration by the registering party, not a pointer to a document: at TCR it attests that the campaign runs no affiliate marketing, and at Bandwidth and Bird that the programme follows the CTIA principles. Its enum permits only true, so an unset flag is rejected before any document is fetched — and because the form shows it next to the URL field, people assume filling one satisfies the other.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "campaign.termsAndConditions attestation",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Bird",
        "Bandwidth",
        "Azure ACS"
      ],
      "codes": [
        {
          "provider": "TCR",
          "code": "501",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the terms-and-conditions attestation to true on the campaign, and make sure the statement it carries is actually true of your programme before you do. Done when both the flag and the URL are set.",
      "example": "terms_and_conditions: true · terms_and_conditions_url: https://acmecoffee.com/sms-terms",
      "pitfalls": [
        "At TCR the flag attests that the campaign involves no affiliate marketing. Setting it true on a campaign that does is a false attestation, not a form-filling step — fix the campaign instead."
      ],
      "notes": "Absorbs POL-006, which states the same flag together with the reminder that the flag alone is not enough. The URL half is POL-002.",
      "catalogIds": [
        "POL-006"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-010",
      "slug": "pol-010",
      "title": "Both policy URLs must resolve and return content anonymously",
      "statement": "The privacy policy and terms URLs must resolve in DNS and return a successful response to an anonymous fetch — no session, no cookie, no member wall.",
      "rationale": "The reviewer's fetcher has no account and no cookies, so a policy behind a member area or a consent wall is indistinguishable from a policy that does not exist — and the rejection says the policy failed review rather than that it was never read. The common causes are all invisible from the inside: a page that requires the site's own cookie banner to be accepted, a URL that 404s after a redesign, a host that resolves only on the office network.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy URL + terms URL fetch",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AT&T",
        "T-Mobile",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "7101",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open both URLs in a private browser window with no session, from a network that is not yours, and fix whatever stops the page rendering. Done when both return the policy text to a plain fetch with no login and no interstitial.",
      "pitfalls": [
        "A page that renders only after the cookie banner is accepted returns an empty body to a fetcher. Serve the policy text before the banner, not behind it."
      ],
      "notes": "Absorbs POL-011 (HTTP status), POL-012 (liveness) and POL-013 (anonymous access): DNS, status and an access wall are three ways one anonymous fetch fails, on either document, with one fix.",
      "catalogIds": [
        "POL-011",
        "POL-012",
        "POL-013"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-015",
      "slug": "pol-015",
      "title": "The policy host must serve a valid certificate of its own",
      "statement": "The host serving the policy pages must present a valid TLS chain, checked independently of the brand website host.",
      "rationale": "Policy pages are routinely hosted somewhere else — a docs subdomain, a help centre, a legal-template service — so the website's certificate says nothing about them, and a certificate failure on the policy host alone is enough to reject a brand whose marketing site is perfect. Bandwidth codes it as a standalone reject reason for exactly that reason.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms URL TLS chain",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Bandwidth",
        "TCR",
        "AT&T",
        "T-Mobile",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7105",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "1103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Renew or reissue the certificate for the policy host, including any subdomain the policy sits on, and test it from outside your network. Done when an external SSL checker reports a complete valid chain for both policy URLs.",
      "pitfalls": [
        "A wildcard certificate for *.acmecoffee.com does not cover a policy hosted on a vendor domain. Check the host in the URL you actually submitted, not the one you think of as yours."
      ],
      "notes": "Severity divergence in the catalog: TCR and the carriers rate a TLS failure MEDIUM, Bandwidth and AWS treat 7105 as a standalone rejection. Strictest kept. The website equivalent is WEB-073, and the two fail independently because the two hosts often differ.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-016",
      "slug": "pol-016",
      "title": "The privacy policy URL must open an actual privacy policy",
      "statement": "The privacy policy URL must resolve to a real privacy policy — not the homepage, a stub, a placeholder, or a generic landing page.",
      "rationale": "A URL that opens something other than a policy is read as a policy that failed review, so the rejection blames the document rather than the link. It happens through ordinary site maintenance: the policy path is retired in a redesign and the server helpfully serves the home page instead of a 404, which is worse than the 404 because nothing looks broken.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (document classification)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "AWS",
        "Ringover"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Point the field at the page whose content is the policy, and confirm what it opens rather than trusting the path. Done when the URL loads a document headed as a privacy policy and containing its sections.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-018",
      "slug": "pol-018",
      "title": "Policy pages must be machine-crawlable",
      "statement": "The policy pages must be fetchable by an automated vetter — no robots.txt disallow, no noindex, no JS-only render, no geofence.",
      "rationale": "Vetting fetches your policy with a headless client, not a browser. A page that renders only after JavaScript, or that robots.txt forbids, is indistinguishable from a page that does not exist — so a perfectly compliant policy fails because the reviewer's crawler saw nothing.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy URL + terms URL",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Serve the policy as server-rendered HTML, remove any robots.txt disallow or noindex on those paths, and confirm the page returns content with JavaScript disabled and from a US IP address.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-020",
      "slug": "pol-020",
      "title": "A non-English site needs an English policy",
      "statement": "Where the site is published in another language, an English version of the policy pages must exist.",
      "rationale": "US 10DLC review is performed in English, and machine translation of a legal document fails in exactly the places that matter — the non-sharing clause reads as a general privacy promise once it has been through a translator. A business serving a non-English-speaking market has every reason to publish only in that language and no reason to expect this.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "policy page language",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish an English version of the privacy policy and SMS terms at their own URLs, and submit those URLs on the campaign. Done when the registered links open English documents, whatever the rest of the site is in.",
      "notes": "Single unverified secondary source in the catalog, hence MEDIUM. The site-side equivalent is WEB-018; both can fail independently, since a bilingual site frequently has monolingual policy pages.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-021",
      "slug": "pol-021",
      "title": "Policy URLs must not redirect",
      "statement": "A URL submitted for a policy page must resolve directly rather than returning a 30x redirect.",
      "rationale": "TCR forbids redirection on fetched URLs, so a link that bounces from http to https, or from a marketing path to a canonical one, can fail even though a human browser follows it invisibly. This is one of the least intuitive rejections because the link works perfectly when you click it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy URL + terms URL",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Submit the final canonical URL — the one you land on after every redirect — including https and any trailing path normalisation.",
      "example": "Submit https://acmecoffee.com/privacy-policy rather than http://acmecoffee.com/privacy",
      "catalogIds": [
        "WEB-075"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-022",
      "slug": "pol-022",
      "title": "The policy must be the brand's own, not a platform's",
      "statement": "A reseller's, platform's, ISV's or generic vendor privacy policy must not be submitted in place of the brand's own.",
      "rationale": "The policy is a promise by the business that collected the number, and a platform's policy promises nothing on the brand's behalf — so the consumer has no commitment from anyone they actually deal with. Telnyx names the specific version of this it sees most: Google's policy submitted as the brand's, because the business runs on Google services and assumed that covered it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (authorship)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Telnyx",
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish a policy in the brand's own name covering the brand's own data handling, and link the platform's policy separately if you want to. Done when the document names the registered business as the party making the promises.",
      "notes": "About authorship, and distinct from POL-021, which is about where the document is hosted. A brand-authored policy hosted on a vendor domain passes this and fails that; a vendor-authored policy on the brand's own domain does the reverse.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-023",
      "slug": "pol-023",
      "title": "A social profile is not a policy page",
      "statement": "A social-media profile or post must not be submitted as the privacy policy or terms URL where the brand has a website.",
      "rationale": "A profile page is not a document: it has no stable URL for a specific text, no version, and no guarantee the platform will show the same thing to a logged-out reviewer. Businesses do it because they published their terms as a pinned post and that is the only link they have, which is exactly the situation the fix addresses.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy URL + terms URL host",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "1100",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the policy as a page on the brand website and submit that URL. Done when the policy URL opens a document on your own domain rather than a profile on somebody else's.",
      "example": "privacy_policy_url: https://acmecoffee.com/privacy, not https://facebook.com/acmecoffee/about",
      "notes": "Single secondary source in the catalog with no carrier code behind it, so HIGH rather than BLOCKING. Fires only where a website exists — a brand with no site at all should upload the document instead, which is POL-003.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-024",
      "slug": "pol-024",
      "title": "The site hosting the policy must itself be live and real",
      "statement": "Where consent is collected offline, the site hosting the policy pages must still be a live, authentic site rather than a shell built to hold them.",
      "rationale": "An offline programme can be flawless on paper and still sink on the site nobody thought was being assessed, because the policy has to live somewhere and that somewhere gets crawled like any other brand site. A single-page host stood up to carry two documents looks exactly like the shell sites the authenticity screen exists to catch.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "policy host site",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Aerialink",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "807",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "qr",
          "pos",
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, QR code, point of sale, live verbal and IVR.",
      "universal": false,
      "remediation": "Host the policy pages on the business's real website. Where there is none, publish a small but genuine site describing the business alongside the documents, rather than a bare page carrying only the policy text.",
      "notes": "Tagged to the offline consent methods, which is where the catalog states it and where it bites: a web opt-in already puts the site itself under WEB-024, so this rule adds nothing there.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-025",
      "slug": "pol-025",
      "title": "Exactly one privacy policy must be discoverable",
      "statement": "The brand domain must present a single discoverable privacy policy, not several copies or versions.",
      "rationale": "Two policies mean a reviewer has to guess which one governs, and Twilio treats that ambiguity as preventing review rather than as a tidiness problem. The usual cause is a platform default left in place beside a custom one — a Shopify or Wix policy page still reachable at its original path while the real one lives somewhere else — so both are live and neither is wrong.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "all discoverable privacy policies on the brand domain",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete or redirect every policy page except the canonical one, including the platform default at its original path, and point every link at the survivor. Done when searching your own site for \"privacy\" returns one document.",
      "notes": "Needs a multi-page crawl to settle. Against a single-page fetch the judge can only report what it saw, so it must say which pages it read rather than treating one policy found as one policy existing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-026",
      "slug": "pol-026",
      "title": "The policy on the site and the policy attached to the registration must agree",
      "statement": "Where a policy document is uploaded with the registration and a policy also exists on the site, the two must not conflict.",
      "rationale": "A reviewer holding two versions reads the weaker one as the truth, so an uploaded document that has been carefully updated does not rescue an old page still live on the site. This happens whenever the upload is prepared for the registration and the website is left alone, which is the normal order of events.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "crawled policy body vs uploaded policy document",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the uploaded version on the site so the two are the same document, and upload the published page rather than a separately maintained file. Done when the attached document and the live page carry identical messaging clauses.",
      "notes": "Different comparison from POL-025: that one is about duplicates on the site, this one about the site disagreeing with the submission. Applies only where a document was uploaded, which the criteria carry as an immediate pass.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-027",
      "slug": "pol-027",
      "title": "The policy URLs must stay live after approval",
      "statement": "The registered privacy policy and terms URLs must remain reachable after the campaign is approved.",
      "rationale": "Approval is not the end of the checking: the URLs are re-crawled afterwards, and a page that disappears triggers re-vetting rather than a request to restore it. Sites get redesigned and paths get renamed months later by people who have never heard of the registration, so the failure arrives long after anyone connects it to a cause.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "registered policy URLs after approval",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep both URLs live for the life of the campaign, and redirect rather than delete if a path has to change. Done when the registered links still open the documents months after approval.",
      "notes": "Absorbs OPS-269, which states the same monitoring duty with its re-vetting consequence. Nothing at submission time can settle it. The user has to add the two registered URLs to whatever they use to watch their own site, and to treat a redesign as a registration event.",
      "catalogIds": [
        "OPS-269"
      ],
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Are the two registered policy URLs on whatever you use to monitor your own site, and does a redesign count as a registration event?",
        "howToCheck": [
          "Add both URLs to your uptime or link monitoring.",
          "Redirect rather than delete if a path has to change.",
          "Tell whoever owns the site that these two paths are registered, because they will be renamed by someone who has never heard of the registration."
        ],
        "failureLooksLike": "A redesign renames a path months after approval. The re-crawl finds nothing, triggers re-vetting rather than a request to restore it, and nobody connects the two events."
      }
    },
    {
      "id": "POL-028",
      "slug": "pol-028",
      "title": "Keep a dated copy of both documents as submitted",
      "statement": "Both policy pages should be captured at submission so the exact version that was reviewed can be produced later.",
      "rationale": "An appeal turns on what the reviewer actually saw, and by the time one is filed the pages have usually been edited — often in response to the rejection itself. Without a capture there is no way to show that the document met the requirement on the day, so a winnable appeal becomes an argument nobody can settle.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms as submitted",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Save a dated PDF or archive capture of both pages at the moment you submit, and keep it with the registration record. Done when you can produce the exact text that was live on the submission date.",
      "notes": "Product hygiene rather than a published carrier rule — the catalog records it as a recommendation. Nothing we hold proves a capture was taken, so this is on the user: print both pages to PDF, or archive them, before pressing submit.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you hold a dated capture of both policy pages exactly as they were on the day you submitted?",
        "howToCheck": [
          "Print both pages to PDF, or take an archive capture, before pressing submit.",
          "Store it with the registration record.",
          "Done when you can produce the exact text that was live on the submission date."
        ],
        "failureLooksLike": "The appeal turns on what the reviewer saw, and by then the pages have been edited — often in response to the rejection itself. A winnable appeal becomes an argument nobody can settle."
      }
    },
    {
      "id": "POL-030",
      "slug": "pol-030",
      "title": "Both documents must be linked from the site footer on every page",
      "statement": "The privacy policy and the terms must be linked from the footer of every page on the brand site.",
      "rationale": "CTIA asks for the policy to be conspicuously displayed and CCPA for a conspicuous link on the homepage, and a footer link satisfies both wherever a consumer happens to land — which for a messaging programme is usually a deep link, not the home page. A policy reachable only from the home page is unreachable from the page the messages point at.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "site footer link set",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "CCPA",
        "Plivo",
        "AT&T",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "805",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put both links in the site-wide footer template rather than on individual pages. Done when an interior page — a product, a post, a landing page — shows both links without navigating anywhere.",
      "notes": "CTIA MPBP §5.2.1 requires conspicuous display; the specific \"every page footer\" formulation comes from a secondary source. The requirement that the policy be reachable at all is WEB-045, which fails a site with no link anywhere.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-041",
      "slug": "pol-041",
      "title": "Offline and keyword opt-in copy must carry the route to the policy",
      "statement": "Verbal, IVR, keyword and email opt-in copy must state where the privacy policy can be found, or carry the disclosures inline.",
      "rationale": "None of these surfaces has a link: a caller cannot click, and a keyword instruction on a poster is read rather than tapped. So the route has to be spoken or printed — a typeable address — or the disclosures have to be said out loud, and a script that mentions neither collects consent from someone who was never told where the terms are.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "verbal script, IVR prompt, keyword CTA or opt-in email body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "consent_disclosure",
      "authorities": [
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "7107",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr",
          "keyword",
          "email"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal, IVR, text-to-join keyword and email.",
      "universal": false,
      "remediation": "Add one sentence to the script or the opt-in copy naming where the terms and privacy policy live, spoken as a typeable address. Done when someone who only heard or read the opt-in knows where to find the policy.",
      "pitfalls": [
        "Naming the policy in the confirmation text is too late — the consumer has already consented. It has to be in the copy that asks."
      ],
      "notes": "Absorbs POL-042, which states the same requirement for keyword and email opt-in. A spoken or printed reference does not substitute for a compliant page: the policy it points at still has to satisfy the rest of this layer.",
      "catalogIds": [
        "POL-042"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-043",
      "slug": "pol-043",
      "title": "Print, QR and point-of-sale calls to action need a typeable policy URL",
      "statement": "A printed, QR or point-of-sale call to action must show a spelled-out, typeable URL for the terms and the privacy policy.",
      "rationale": "A hyperlink does not exist on paper. The consumer standing at a counter or holding a flyer can only type what they can read, so a URL rendered as \"click here\", hidden behind a QR code, or shortened past recognition leaves them with no way to reach the terms at all — and the reviewer with nothing to follow either.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "printed CTA artifact or POS screen",
      "severity": "BLOCKING",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "consent_disclosure",
      "authorities": [
        "CTIA",
        "Postscript",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "805",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "paper",
          "qr",
          "pos"
        ]
      },
      "applicabilityText": "Applies when consent was collected by paper form, QR code and point of sale.",
      "universal": false,
      "remediation": "Print both addresses in full and legibly beside the call to action, short enough to type. Done when someone reading the printed piece can type the policy address without a phone.",
      "example": "Terms: acmecoffee.com/sms-terms · Privacy: acmecoffee.com/privacy",
      "pitfalls": [
        "Aerialink accepts the inline non-sharing statement printed instead of the privacy URL, but only that specific substitution — the terms address is still required."
      ],
      "notes": "VISION because it is a question about the artifact as printed: whether the address is legible and typeable cannot be answered from a text transcript of it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-044",
      "slug": "pol-044",
      "title": "The QR landing page must link a compliant policy and message-programme terms",
      "statement": "A QR opt-in landing page must link both a compliant privacy policy and compliant message-programme terms.",
      "rationale": "The landing page is the entire disclosure surface for a QR opt-in — there is no checkout, no footer the consumer arrived through, and nothing behind them. If the two documents are not linked there, the consumer consents having seen neither, and the printed piece cannot carry them because it has no links.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "QR landing page link set",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Aerialink",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "852",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Put both links next to the consent control on the landing page, not in a footer below the fold. Done when the page that collects the number also shows the way to both documents.",
      "notes": "The linked documents still have to satisfy the rest of this layer — a link to a non-compliant policy discharges this rule and fails POL-050 onward.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-045",
      "slug": "pol-045",
      "title": "An offline opt-in does not relax the policy content requirements",
      "statement": "The policy pages must satisfy the full content requirements even where opt-in never happens on the web.",
      "rationale": "Programmes that collect consent on a call, on paper or at a counter tend to treat the website as decoration, so the policy is whatever was published years ago — and the reviewer applies exactly the same content checks to it as to an e-commerce brand. Nothing about collecting consent offline reduces what the consumer is entitled to read.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body + terms body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS",
        "Twilio",
        "Bandwidth"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr",
          "paper",
          "pos",
          "imported"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal, IVR, paper form, point of sale and imported list.",
      "universal": false,
      "remediation": "Bring the published policy and SMS terms up to the same standard a web opt-in would need — the mobile non-sharing clause, the programme description, frequency, rates, STOP and HELP — before submitting the offline flow. Done when the documents would pass on their own, with the consent method left out of it.",
      "notes": "An anti-exemption rule: it exists to be applied to registrations that assume the requirements are lighter, and it points at the rest of the layer rather than adding a requirement of its own.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-050",
      "slug": "pol-050",
      "title": "Privacy policy must state mobile opt-in data is not shared or sold",
      "statement": "The privacy policy must explicitly state that mobile information and SMS consent are not shared or sold to third parties or affiliates for marketing.",
      "rationale": "This is the single most frequently cited cause of 10DLC rejection in the entire corpus. Carriers require it because SMS consent is non-transferable: the clause is the written promise that the number you collected will not become someone else's marketing list. Note it is a carrier and TCR requirement, not a CTIA one — CTIA never asks for this sentence, so guidance attributing it there is wrong and leads people to skip it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio",
        "T-Mobile",
        "Bandwidth",
        "AWS",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        },
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add an explicit mobile-data clause to the privacy policy. Paste the language below verbatim — reviewers look for this specific formulation, and paraphrases that soften \"will not be shared\" routinely fail.",
      "example": "No mobile information will be shared with third parties or affiliates for marketing or promotional purposes. All the above categories exclude text messaging originator opt-in data and consent; this information will not be shared with any third parties.",
      "catalogIds": [
        "POL-051",
        "POL-055",
        "POL-056",
        "POL-057"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-053",
      "slug": "pol-053",
      "title": "The clause must say NOT SHARED, not merely \"not sold\"",
      "statement": "The non-sharing statement must separately state that data is not shared — \"we do not sell your data\" alone is insufficient.",
      "rationale": "Sharing covers non-monetary transfer, including passing numbers to an affiliate or sister company at no charge. A policy promising only not to SELL leaves the most common abuse route wide open, which is exactly why reviewers require both words.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "T-Mobile",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Amend the sentence to cover both verbs explicitly: \"will not be sold, rented, or shared\".",
      "example": "We do not sell, rent, or share your mobile phone number or SMS consent with third parties or affiliates.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-058",
      "slug": "pol-058",
      "title": "A service-provider carve-back is required, not prohibited",
      "statement": "The policy should permit disclosure to the vendors that actually deliver the messages, while prohibiting everything else.",
      "rationale": "An absolute \"we never disclose your number to anyone\" is factually false — your platform, aggregator and the carriers all handle it — and a reviewer who spots the impossibility distrusts the rest of the policy. The correct shape is a narrow carve-back for message delivery.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a narrow carve-back naming the delivery chain, immediately after the non-sharing sentence.",
      "example": "We share your mobile number only with the service providers who deliver our messages (our messaging platform, aggregators, and wireless carriers), and for no other purpose.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-060",
      "slug": "pol-060",
      "title": "No exception may permit sharing SMS opt-in data",
      "statement": "The privacy policy must contain no exception, carve-out or \"except where\" clause that permits SMS opt-in data to be shared.",
      "rationale": "Bandwidth's reviewer reads the prohibition as admitting no exception at all, so a policy that promises not to share and then lists the circumstances in which it will is read on the list rather than on the promise. The exceptions are almost always drafted for good reasons — a merger, a partner integration, an analytics vendor — and each one reopens exactly the door the clause was written to close.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (whole document)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Bandwidth",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "7109",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete every exception attached to the messaging non-sharing clause, keeping only the narrow service-provider carve-back for the vendors that deliver the messages. Done when the clause has no \"except\", \"unless\" or \"other than\" hanging off it besides that one.",
      "example": "No mobile information will be shared with third parties or affiliates for marketing or promotional purposes. We disclose it only to the service providers who deliver our messages, and to no one else.",
      "pitfalls": [
        "A merger or acquisition clause counts as an exception here, even though it is standard in every other part of a privacy policy. Exclude messaging data from it explicitly rather than relying on it being obviously different."
      ],
      "notes": "Genuine implementation divergence, recorded rather than resolved: Bandwidth 7109 as written admits no exception, while Infobip, Telnyx and Alive5 require the service-provider carve-back that POL-058 asks for. The product has to draft that carve-back narrowly enough to survive a 7109 reviewer, and a policy carrying both patterns is worth a human look. Absorbs POL-059, which names the common shape — an exception for partners, advertisers or lead buyers.",
      "catalogIds": [
        "POL-059"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-061",
      "slug": "pol-061",
      "title": "The non-sharing prohibition must hold even with consumer consent",
      "statement": "The prohibition must be unconditional — an \"unless you consent\" or \"except where you have authorised\" escape clause fails.",
      "rationale": "A consent-conditional promise is not a promise: any dark-pattern checkbox re-opens the sharing the clause was meant to close. Carriers require the prohibition to be absolute because consumer \"authorisation\" to share is precisely the mechanism lead-generation operations use.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete any conditional qualifier from the non-sharing sentence so it reads as an unconditional commitment.",
      "example": "No mobile information will be shared with third parties or affiliates for marketing or promotional purposes.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-062",
      "slug": "pol-062",
      "title": "A general permission to share data with other companies fails even where SMS is never named",
      "statement": "A policy permitting personal data to be shared with other companies is rejected whether or not it mentions SMS.",
      "rationale": "Naming SMS is what makes a non-sharing clause count in the brand's favour, but not naming it does not let a general sharing clause escape — the phone number is personal data, so a permission covering personal data covers it. Businesses reason the opposite way round, adding a careful SMS paragraph while leaving the general sharing section untouched because it says nothing about texting.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (whole document)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Narrow the general sharing section to named categories of service providers acting for you, or exclude messaging data from it in the section itself. Done when no clause in the document permits personal data to reach another company for that company's own use.",
      "example": "We share personal information only with service providers acting on our behalf. We do not share personal information with other companies for their own purposes, and text messaging opt-in data and consent are excluded from all sharing.",
      "notes": "Fenced against its neighbours: POL-064 is about clauses permitting sharing FOR MARKETING, this one about a general permission with no purpose stated; POL-069 is the contradiction between such a clause and the SMS one, and POL-070 the same contradiction across two documents.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-063",
      "slug": "pol-063",
      "title": "A policy that says mobile opt-in data is shared is refused outright",
      "statement": "A privacy policy stating affirmatively that mobile opt-in data or consent is shared with third parties or affiliates is rejected.",
      "rationale": "This is the loudest version of the defect and it is treated as evidence about the business rather than as bad drafting — AWS maps it to a spam-association reason, not to a document problem. It is what a lead-generation operation's policy says when it is being honest, so a legitimate business with an over-broad template inherits that reading without having done anything.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Sinch",
        "AWS",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete the sentence and replace it with the non-sharing clause, then check the business actually operates that way before publishing it. Done when nothing in the document says mobile data goes to anyone but the delivery vendors.",
      "example": "Mobile information, including your number and your consent to receive text messages, is never sold, rented, traded or shared with third parties or affiliates for their own marketing or promotional purposes.",
      "pitfalls": [
        "Do not fix this by deleting the sentence while the practice continues. The rule that follows is POL-108, and a policy that misdescribes what the business does is a worse position than an accurate one."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-064",
      "slug": "pol-064",
      "title": "No clause anywhere in the policy may permit sharing data for third-party marketing",
      "statement": "The whole policy — not just the SMS section — must contain no clause permitting sale, rental, or sharing of personal data for third-party marketing.",
      "rationale": "Ranked the #3 most-missed check in the catalog. Policies routinely carry the required non-sharing sentence in an SMS block while a general data-sharing clause three sections earlier says the opposite. Reviewers read the whole document and treat the permissive clause as controlling, so a tool that only looks for the good sentence passes a policy that will be rejected.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (whole document)",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio",
        "T-Mobile",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Find and remove or narrow every clause permitting data sharing for marketing. Where you must keep a sharing clause for other purposes, carve messaging data out of it explicitly with \"excluding text messaging originator opt-in data and consent\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-065",
      "slug": "pol-065",
      "title": "Boilerplate \"trusted partners\" language must be removed or narrowed",
      "statement": "Generic policy-generator output such as \"we may share with trusted partners\" must be removed or narrowed to service providers.",
      "rationale": "Ranked #4 most-missed. Free privacy-policy generators emit permissive sharing language by default, and the business pasting it in has no idea it contradicts their messaging programme. It is the most common source of the inverse trap because nobody wrote the sentence deliberately.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace vague partner language with a specific, closed list of processor categories, and state that messaging data is excluded from all of them.",
      "example": "We share personal information only with service providers who perform functions on our behalf (payment processing, order fulfilment, and message delivery). We do not share personal information with third parties for their own marketing purposes.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-066",
      "slug": "pol-066",
      "title": "CCPA sold/shared disclosure must exclude phone numbers and SMS consent",
      "statement": "Where the policy carries a CCPA \"categories of personal information sold or shared\" table, it must explicitly exclude phone numbers and SMS consent data.",
      "rationale": "Ranked #5 most-missed. A CCPA table listing \"identifiers\" as sold or shared directly contradicts the non-sharing promise, because a phone number is an identifier. Businesses complying carefully with California law thereby create a 10DLC rejection, and the fix is a carve-out rather than a removal.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy CCPA disclosure",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add an explicit exclusion beneath the CCPA table so the two disclosures do not contradict each other.",
      "example": "All the above categories exclude text messaging originator opt-in data and consent; this information will not be shared with any third parties.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-067",
      "slug": "pol-067",
      "title": "A Do-Not-Sell link must not appear to reopen the SMS carve-out",
      "statement": "Where the brand publishes a \"Do Not Sell or Share My Personal Information\" link, the policy must make clear that the SMS exclusion applies regardless.",
      "rationale": "The link is an admission that the business sells or shares something, and a reviewer who finds it reads the messaging carve-out as one of the things a consumer must opt out of rather than as a promise already made. Businesses publishing it are complying carefully with California law, which is what makes this trap so unfair: the more diligent the CCPA work, the more likely the 10DLC rejection.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body + Do-Not-Sell link",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State next to the Do-Not-Sell disclosure that messaging opt-in data is never sold or shared and is therefore outside the request entirely. Done when a reader can see the exclusion holds without submitting anything.",
      "example": "We honour Do Not Sell or Share requests for the categories listed above. Text messaging opt-in data and consent are never sold or shared in any case, so no request is needed for them.",
      "notes": "Conditional on the brand publishing the link, which no applicability dimension expresses; the criteria pass immediately where no such link exists. The catalog records the rule as a research judgement grounded in CCPA §7011 and Twilio 30932 rather than as published vendor wording.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-068",
      "slug": "pol-068",
      "title": "An override sentence should say the SMS clause prevails",
      "statement": "The policy should state explicitly that the SMS non-sharing clause prevails over any other sharing language in the document.",
      "rationale": "Large policies are edited by several people over years, and no amount of care stops a future section from contradicting the messaging clause. An override sentence is what makes the document survive that — it decides the contradiction in advance, in the brand's favour, instead of leaving a reviewer to decide it against them.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy SMS section",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add one sentence to the messaging section saying it controls wherever the rest of the document says otherwise. Done when the policy answers the question \"which clause wins?\" without anyone having to interpret it.",
      "example": "Where anything else in this policy appears to permit sharing of personal information, this section controls for mobile numbers, text messaging opt-in data and consent.",
      "notes": "Sourced from the research's own generation-validation table rather than from a published carrier rule: it is the mitigation for POL-064, POL-066 and POL-069 rather than an independent requirement, which is why it is graded HIGH and worded as an expectation.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-069",
      "slug": "pol-069",
      "title": "The general policy and the SMS clause must not contradict each other",
      "statement": "Where the policy contains both a general sharing disclosure and an SMS-specific non-sharing clause, the two must be reconciled rather than left to contradict.",
      "rationale": "A document that promises two opposite things has told the reviewer nothing except that nobody read it end to end, and the reviewer resolves it against the brand by taking the permissive clause as controlling. It is the standard outcome of adding a compliance paragraph to an existing policy: the new text is correct, the old text is untouched, and both are live.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (intra-document consistency)",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Edit the general section so it agrees with the SMS clause — narrow it, or exclude messaging data from it by name — rather than adding a third paragraph explaining the relationship. Done when a reader can find only one answer to \"is my number shared?\".",
      "example": "General section: \"We share personal information with service providers acting on our behalf, and with no one else. Text messaging opt-in data and consent are excluded from all sharing and disclosure.\"",
      "notes": "The contradiction itself is the finding, which is why this is separate from POL-062 and POL-064: those fail the permissive clause on its own terms, and this one fails the document for holding both. Where only the permissive clause exists, report it there and pass here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-070",
      "slug": "pol-070",
      "title": "No other document on the domain may contradict the SMS non-sharing clause",
      "statement": "The terms of service, the cookie policy and any separate data-sharing page must not contradict the SMS non-sharing clause in the privacy policy.",
      "rationale": "Reviewers read the whole domain, not the one URL on the form, so a data-sharing page written for advertisers overrides a perfect privacy policy. The other documents are usually owned by other people — marketing writes the cookie notice, legal writes the terms — and nobody is comparing them against a clause added for a messaging registration.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "all policy-bearing pages on the brand domain",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Search every policy-bearing page on the domain for sharing language and bring each into line with the messaging clause. Done when the terms, the cookie notice and any data-sharing page all say the same thing about mobile data.",
      "pitfalls": [
        "The cookie policy is the one people forget, because it feels unrelated to SMS — and it is the one most likely to carry a permissive advertising-partner clause."
      ],
      "notes": "Needs a multi-page crawl. Against a single-page fetch the judge can compare only the documents it holds — the privacy policy and the terms — and must say which pages it could not read rather than clearing the domain.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-071",
      "slug": "pol-071",
      "title": "No document may reserve the right to sell or share consent itself",
      "statement": "Neither the privacy policy nor the terms may reserve a right to sell, rent or share the consumer's consent.",
      "rationale": "Consent names a sender; it is not an asset that can change hands, and a document claiming otherwise describes the business model the whole framework exists to stop. Twilio treats this as making the business noncompliant rather than the document, so it is a much worse finding than an over-broad data-sharing clause — and it usually arrives through a template borrowed from an affiliate network.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy and terms body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "T-Mobile",
        "AT&T",
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete any clause reserving rights over the consent itself — to sell it, rent it, share it, or pass it to partners — from both documents. Done when the only thing either document says about consent is how to give it and how to withdraw it.",
      "example": "Your consent to receive text messages from Acme Coffee applies only to Acme Coffee. We do not sell, rent, share or transfer it to anyone.",
      "notes": "Distinct from the data-sharing rules: those are about the number and what is known about the person, this one about the permission. A policy can be impeccable about data and still claim a right over the consent.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-072",
      "slug": "pol-072",
      "title": "Consent must not be described as transferable or assignable",
      "statement": "The policy and the SMS terms must not describe messaging consent as transferable or assignable between businesses.",
      "rationale": "CTIA states plainly that an opt-in should not be transferable or assignable, because the consumer agreed to hear from one named business and cannot be taken to have agreed to hear from its successors or affiliates. The wording usually arrives inside a boilerplate assignment clause covering the whole agreement, drafted with contracts in mind rather than consent.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy and SMS terms body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Exclude consent from the assignment clause explicitly, and state in the SMS terms that the opt-in applies to this brand alone. Done when no clause lets the consent travel with a sale of the business.",
      "example": "We may assign this agreement, but your text messaging consent applies only to Acme Coffee and is not transferred or assigned with it.",
      "notes": "The positive form of the same requirement is POL-191, which asks the SMS terms to state that the opt-in is programme-specific. This one fails a document that says the opposite; that one fails a document that says nothing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-073",
      "slug": "pol-073",
      "title": "The policy should say the brand does not use rented or purchased lists",
      "statement": "The policy or SMS terms should state affirmatively that the brand does not message rented, sold or shared opt-in lists.",
      "rationale": "CTIA asks senders not to use such lists, and the affirmative statement is what a reviewer can actually check — the absence of purchased numbers is not visible in any document. It also matters to the consumer question underneath: someone receiving a message wants to know how the sender got their number, and this is the sentence that answers it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy or SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7108",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a sentence to the messaging section stating that every number was collected directly from the person it belongs to and that no rented, purchased or shared lists are used. Done when the document says where the numbers come from.",
      "example": "Every number in the Acme Coffee text programme was given to us directly by its owner. We do not rent, buy or use lists of numbers collected by anyone else.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-074",
      "slug": "pol-074",
      "title": "An explicit SMS opt-in carve-out belongs in the policy either way",
      "statement": "The policy must carry an explicit sentence excluding SMS opt-in data and consent from any sharing, whether or not it discloses sharing elsewhere.",
      "rationale": "Reviewers grep for this sentence, so a policy that is silent about sharing gets no credit for its silence — the absence of a bad clause is not the presence of the promise. Telnyx publishes the exact wording, which is a strong hint that what is being checked is the formulation rather than the sentiment, and paraphrases that soften it routinely fail.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy carve-out sentence",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Infobip",
        "Bird",
        "Telnyx",
        "Aerialink",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "852",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Paste the published carve-out sentence immediately after whatever the policy says about sharing — or, in a policy that discloses none, at the end of the data-collection section. Done when the words \"text messaging originator opt-in data and consent\" appear in the document.",
      "example": "All the above categories exclude text messaging originator opt-in data and consent; this information will not be shared with any third parties.",
      "pitfalls": [
        "Placing the sentence in a section of its own, far from the sharing disclosure, weakens it: the point is that it visibly overrides the categories immediately above it."
      ],
      "notes": "Absorbs POL-075, which states the same requirement for a policy that discloses no sharing at all. The sentence to add is identical in both cases, which is why they are one obligation.",
      "catalogIds": [
        "POL-075"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-076",
      "slug": "pol-076",
      "title": "The privacy policy must describe data handling, not sell the programme",
      "statement": "The privacy policy must describe how data is handled rather than marketing the messaging programme.",
      "rationale": "A policy written as promotional copy — how great the offers are, why to sign up — answers none of the questions a policy exists to answer, so a reviewer looking for the data-handling terms finds none and rejects the document. It happens when the page is written by whoever writes the rest of the site, from a brief that said \"explain the text programme\".",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body (document purpose)",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Rewrite the page around what is collected, why, who it goes to and how to get it removed, and move the programme pitch to a marketing page. Done when the document reads as a description of data handling rather than an invitation to join.",
      "example": "When you join the Acme Coffee text programme we collect your mobile number, the date and source of your consent, and the delivery status of the messages we send. We use them only to send the messages you asked for.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-078",
      "slug": "pol-078",
      "title": "A political brand is held to the same data-sharing ban",
      "statement": "The third-party data-sharing prohibition applies to a political brand's policy exactly as it does to a commercial one.",
      "rationale": "Political organisations routinely share supporter lists with allied committees and vendors, and treat it as normal practice rather than as data sharing — Twilio names political campaigns that buy, sell or share consumer information specifically. The consumer protection is identical: someone who consented to hear from one committee did not consent to hear from every committee it works with.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Exclude mobile numbers and messaging consent from every list-exchange, coalition or vendor-sharing clause in the policy, and stop the practice for the numbers in this programme. Done when the policy says supporter mobile data is not shared with allied organisations.",
      "notes": "An anti-exemption rule. It exists because the sector reads the general prohibition as aimed at commercial marketing, so stating it for political brands separately is what makes it land.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-079",
      "slug": "pol-079",
      "title": "A charity policy must bar anyone but the charity from using subscriber data",
      "statement": "A charity or donation programme's policy must state that no entity other than the charity itself may use the subscriber data.",
      "rationale": "T-Mobile requires this because donor lists are traded more freely than almost any other consumer data, and the donor who gave a number to one cause did not agree to hear from every cause its fundraising agency serves. Charities work through agencies and platforms as a matter of course, so the clause has to say the agency may deliver the messages and nothing else.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "State in the policy that subscriber data is used only by the named charity, and that agencies and platforms handle it solely to deliver the charity's own messages. Done when no other organisation is permitted to use it for anything.",
      "example": "Numbers given to Riverside Shelter are used only by Riverside Shelter. Our messaging vendor sends the texts on our behalf and uses the numbers for nothing else; no other organisation receives them.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-080",
      "slug": "pol-080",
      "title": "A political donation programme must put its policy in front of donors",
      "statement": "A political organisation soliciting donations by text must communicate its privacy policy to donors and enforce it.",
      "rationale": "CTIA's political guidelines ask for more than a published document: the donor giving money over a text message has to be shown the policy at the point of giving, because that is the moment their number, their name and their giving history are collected together. Political programmes are run at speed by volunteers and consultants, so the policy is usually a link in a footer nobody following a donation link ever sees.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body + donor-facing disclosure",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Link the policy on the donation page itself and in the messages that ask for money, and hold the programme to it — including the third-party sharing ban that POL-078 applies. Done when a donor can reach the policy from the page they give on.",
      "pitfalls": [
        "A link in the site footer is not communication to a donor arriving from a text on a donation URL. Put it on the donation page."
      ],
      "notes": "Conditional on donations being solicited as well as on the political use case. The use-case half is tagged; the donation half sits in the criteria, which pass immediately for a political programme that asks for nothing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-090",
      "slug": "pol-090",
      "title": "The policy must say what messaging data is collected",
      "statement": "The privacy policy must describe what data the messaging programme collects.",
      "rationale": "A consumer handing over a number is entitled to know that the consent timestamp, the source, the delivery status and in many programmes the message content are kept alongside it — none of which is obvious from typing a phone number into a box. Twilio asks directly for \"what customer data you collect\", and a policy that names only \"contact information\" answers a different question.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a short list to the data-collection section naming the messaging fields specifically. Done when a reader can see that consent metadata is kept, not just the number.",
      "example": "For the Acme Coffee text programme we collect your mobile number, the date and time you consented, the page or call it happened on, and the delivery status of each message we send.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-091",
      "slug": "pol-091",
      "title": "The policy must say how the number was obtained",
      "statement": "The privacy policy must describe how phone numbers are obtained, consistently with the registered message flow.",
      "rationale": "This is the sentence a reviewer compares against the message flow, and a disagreement between them reads as two different programmes — the registration says checkout, the policy says \"when you contact us\". It is also the answer to the first question anyone asks about an unexpected text, which is how did you get my number.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name every collection surface the registration declares, using the same words the message flow uses. Done when the policy and the message flow describe the same opt-in.",
      "example": "We collect your mobile number when you tick the text-messaging box at checkout on acmecoffee.com. That is the only way we add a number to the programme.",
      "notes": "The SMS-terms twin is POL-181, which asks the terms to describe every method, and POL-232 asks the same of the surfaces found by crawling. All three can disagree independently, which is why they are separate.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-092",
      "slug": "pol-092",
      "title": "The policy must say what the messages are for",
      "statement": "The privacy policy must describe the purpose of the texting, consistently with the registered use case.",
      "rationale": "The use case sets the consent standard the campaign is held to, so a policy describing promotions behind a transactional registration tells the reviewer the wrong standard was applied. From the consumer's side it is the difference between agreeing to delivery updates and agreeing to weekly marketing, which is the whole substance of what they consented to.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS",
        "TCR",
        "Twilio",
        "Klaviyo"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the message categories in the policy and check them against the registered use case before submitting. Done when the two describe the same programme.",
      "example": "We use your number to send Acme Coffee promotional offers and rewards balance updates — the programme you joined at checkout — and for nothing else.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-093",
      "slug": "pol-093",
      "title": "A non-marketing programme's policy must not describe promotional texting",
      "statement": "Where the campaign is not registered for marketing, the policy must not describe promotional or marketing texting.",
      "rationale": "The inverse of the use-case match, and the one with the different fix: here the policy is broader than the registration, so a reviewer reads it as a marketing programme registered as transactional to get an easier consent standard. It usually arrives through a policy written for the whole business being pointed at a single transactional campaign.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body vs campaign.usecase",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS"
      ],
      "applicability": {
        "excludeUseCases": [
          "MARKETING",
          "MIXED",
          "SWEEPSTAKE"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT MARKETING, MIXED and SWEEPSTAKE.",
      "universal": false,
      "remediation": "Either narrow the policy so its messaging section describes only the transactional programme, or register the marketing use case and collect promotional-grade consent. Done when the policy claims no more than the registration does.",
      "example": "Non-marketing policy text: \"We text you about your open support ticket. We do not send promotional messages to numbers collected this way.\"",
      "notes": "The converse of POL-077, which forces the marketing use case when the policy mentions promotions. Same pair of documents, opposite direction, different fix — narrow the policy rather than widen the registration.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-094",
      "slug": "pol-094",
      "title": "The policy must describe how information is collected, used and shared",
      "statement": "The privacy policy must describe how the sender collects, uses and shares consumer information.",
      "rationale": "This is CTIA's actual privacy requirement, and it is far weaker than the carrier non-sharing overlay everyone argues about — which is why it gets skipped: businesses focused on the non-sharing sentence forget the document also has to describe ordinary handling. A policy that promises not to share and never says what it does with the data has answered only half the question.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA",
        "Bandwidth",
        "Klaviyo"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the document the three ordinary sections — collection, use, disclosure — around whatever it already says about messaging. Done when a reader can follow the data from where it is collected to who ends up handling it.",
      "example": "We collect your number at checkout, use it to send the messages you asked for, and disclose it only to the messaging vendor that delivers them.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-095",
      "slug": "pol-095",
      "title": "The policy must say how to contact the sender",
      "statement": "The privacy policy must describe how consumers can contact the sender about their information.",
      "rationale": "Every right the policy grants — access, deletion, opting out — depends on there being someone to ask, so a document with no contact route grants nothing it can deliver. The address is also what a reviewer uses to corroborate the brand, which is why a bare web form fails where an email address on the brand domain passes.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Bandwidth",
        "Sakari",
        "MessageDesk"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a privacy contact block with an email address on the brand domain and the business postal address. Done when a reader can write to a person without using a form.",
      "example": "Privacy questions: privacy@acmecoffee.com, or Acme Coffee Co, LLC, 1240 Mission St, Suite 400, San Francisco, CA 94103.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-096",
      "slug": "pol-096",
      "title": "The policy must state who the business is",
      "statement": "The privacy policy must identify the business: legal name, any DBA, postal address, email address and phone number.",
      "rationale": "The identity block is the mechanism by which a vetter binds a policy to a brand record — without it, the document could belong to anyone, and a reviewer comparing it against the registration has nothing to compare. It is also what a consumer needs in order to exercise any of the rights the rest of the document grants them: a policy that promises deletion and names nobody to ask is not a promise.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy identity block",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "GDPR",
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "807",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a \"Who we are\" block carrying the registered legal name, the trading name, the postal address, a privacy email and a phone number, and make those values the same as the brand record. Done when a reader can identify and contact the business without leaving the document.",
      "example": "Acme Coffee Co, LLC, trading as Acme Coffee · 1240 Mission St, Suite 400, San Francisco, CA 94103 · support@acmecoffee.com · 415-555-0134",
      "pitfalls": [
        "A contact form link is not a contact route for this purpose — the address and at least one direct channel have to be in the text, because the reviewer is matching strings against the brand record."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-097",
      "slug": "pol-097",
      "title": "The policy must carry opt-out instructions of its own",
      "statement": "The privacy policy must give opt-out instructions inside the policy itself.",
      "rationale": "The policy is where someone looks when the last message has scrolled away and they want out, so pointing at the messages rather than restating the route leaves them where they started. It costs one sentence and it removes the most common reason a consumer complains to the carrier instead of to the brand.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio",
        "Ringover",
        "Sakari",
        "MessageDesk"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the keyword and one other route to the messaging section of the policy. Done when a reader who has never received a message still knows how to stop them.",
      "example": "Reply STOP to any Acme Coffee message to unsubscribe, or email support@acmecoffee.com and we will remove you.",
      "notes": "Naming one route as the only one fails POL-117 — list the keyword and say other reasonable requests are honoured too.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-098",
      "slug": "pol-098",
      "title": "The policy must disclose message frequency",
      "statement": "The privacy policy must disclose how often the programme sends messages.",
      "rationale": "Twilio asks for the frequency in the privacy policy while CTIA locates it in the terms, so a business that put it in one document and not the other is complying with a real standard and failing a different one. The consumer interest is the same in both places: how often is the thing they just agreed to going to reach them.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the frequency claim to the messaging section of the policy, worded identically to the one in the SMS terms and at the opt-in. Done when all three say the same thing.",
      "example": "Message frequency varies.",
      "notes": "A genuine provider divergence, resolved by requiring it in both documents: Twilio 30908 and 30909 demand it in the privacy policy, CTIA in the terms and conditions. POL-172 is the terms-side rule.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-099",
      "slug": "pol-099",
      "title": "The policy must carry the message-and-data-rates disclosure",
      "statement": "The privacy policy must carry the \"message and data rates may apply\" disclosure.",
      "rationale": "The disclosure exists so nobody is billed by their carrier for something they did not know they were agreeing to, and it is required in the policy as well as the terms for the same reason frequency is: the two standards disagree about where it lives, so it has to be in both. It is one sentence and it is the single most mechanically-screened string in the framework.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30924",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence to the messaging section of the policy. Done when the phrase appears in the policy as well as in the SMS terms.",
      "example": "Privacy policy, \"Text messaging\" section: \"Acme Coffee Rewards is a recurring programme. Message and data rates may apply.\"",
      "notes": "The catalog exempts Free-To-End-User programmes, where the consumer is not billed. We hold no FTEU fact, so the rule fires universally; an FTEU programme should record the exemption rather than treating the finding as wrong.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-101",
      "slug": "pol-101",
      "title": "The registered brand name must appear in the privacy policy",
      "statement": "The registered brand name or DBA must appear in the privacy policy the campaign links to.",
      "rationale": "A vetter matches the name on the registration against the name in the policy, and a mismatch is reported as a policy mismatch rather than as a naming question — so a business trading under a shorter name than it registered fails on two documents that are both correct. It is also what tells the consumer whose promise they are reading, which the campaign's samples cannot do for them.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body vs brand.displayName / brand.dba",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "AWS",
        "Sinch",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the registered brand name or DBA in the policy text — the identity block is the natural place — and register whichever name the document uses as the brand DBA. Done when the name on the campaign and the name in the policy are the same string.",
      "example": "The policy names \"Acme Coffee\", which is the registered DBA of Acme Coffee Co, LLC.",
      "notes": "Absorbs POL-100 (the policy names the company the consumer is consenting to hear from) and BRD-142 (the same entity-naming requirement across the policy and terms). POL-225 makes the stricter comparison across all three documents; this rule is the one-document form, so a brand whose terms page is missing still gets a specific finding here.",
      "catalogIds": [
        "POL-100",
        "BRD-142"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-104",
      "slug": "pol-104",
      "title": "The policy must say how message content is handled",
      "statement": "The privacy policy must state how message content is stored, for how long, who can read it, and whether it is used for analytics or AI.",
      "rationale": "Two-way programmes accumulate conversations that people treat as private, and the number of platforms now feeding message content to analytics or model training makes silence on this a live consumer question rather than a formality. A business that has never thought about it usually finds the answer is in its vendor's settings rather than in its own practice.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add two sentences to the messaging section covering storage, access and any secondary use. Check what your platform actually does before writing them. Done when the policy describes the real handling rather than the intended handling.",
      "example": "Replies to our texts are stored in our support system for 24 months and are read only by the Acme Coffee support team. We do not use message content for advertising or to train automated systems.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-105",
      "slug": "pol-105",
      "title": "The policy must describe its security safeguards at a high level",
      "statement": "The privacy policy must describe the safeguards protecting the information, at least at a high level.",
      "rationale": "CTIA asks senders to describe how information is protected, and the value to the consumer is a signal that somebody thought about it at all. The trap is the opposite of omission: a generated policy that invents a certification the business does not hold turns a missing sentence into a false statement, which is a much worse position.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA",
        "MessageDesk"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the measures you actually have in one or two sentences, and name no standard you have not been audited against. Done when everything the section claims is true of your systems today.",
      "example": "We encrypt data in transit and at rest, and limit access to the numbers in the text programme to the staff who operate it.",
      "pitfalls": [
        "Do not name a compliance standard to fill the section. POL-242 exists because invented certifications are the specific failure this section attracts."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-106",
      "slug": "pol-106",
      "title": "The policy must state retention and consumer rights",
      "statement": "The privacy policy must state how long data is kept and how consumers can access or delete it.",
      "rationale": "Retention and deletion are what a consumer actually wants after they have unsubscribed — the number is out of the programme, and they want to know whether it is gone. Several statutes require the disclosure independently, and the COPPA amendments make a published retention policy mandatory wherever children's data is in scope.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Sakari",
        "GDPR",
        "FTC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State a period or a rule for messaging data, and give the route for access and deletion requests. Done when a reader knows how long you keep their number and how to ask you to delete it.",
      "example": "We keep opt-in records for four years after you unsubscribe, because we may need to show that consent existed. Email privacy@acmecoffee.com to see or delete what we hold.",
      "pitfalls": [
        "Consent records are usually the one thing that should outlive a deletion request, because they are the evidence that the messages were lawful. Say so, rather than promising a deletion you will not perform."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-107",
      "slug": "pol-107",
      "title": "The policy must be consistent with applicable privacy law",
      "statement": "The privacy policy must be consistent with the privacy law that applies to the business.",
      "rationale": "CTIA requires it, and which law applies turns on where the customers are, what is collected and how large the business is — none of which a document check can establish. The consequence of getting it wrong is a regulator rather than a carrier, so it is the one requirement in this layer where passing the registration is not the point.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Have the policy reviewed by someone who knows which statutes apply to your business, before publishing rather than after a complaint. Done when a reviewer has confirmed the applicable regimes are covered.",
      "notes": "Not machine-decidable and not something the registry should pretend to settle. The individual statutory elements this layer does check — the CCPA sections, the GDPR addendum, the COPPA sections — are a floor rather than a legal opinion, and the user has to route the document to counsel for the rest.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has someone who knows which privacy statutes apply to this business read the policy?",
        "howToCheck": [
          "Establish which regimes are in play: where the customers are, what is collected, and how large the business is.",
          "Route the document to counsel or a qualified privacy adviser before publishing, not after a complaint.",
          "Treat the CCPA, GDPR and COPPA sections this registry checks as a floor, not a legal opinion."
        ],
        "failureLooksLike": "The document passes every automated check and misses the regime the business is actually in. The consequence here is a regulator rather than a carrier, so approval of the registration is not the point."
      }
    },
    {
      "id": "POL-108",
      "slug": "pol-108",
      "title": "The policy must describe what the business actually does",
      "statement": "The practices described in the policy must match what the site and the programme actually do.",
      "rationale": "A policy that misdescribes the business is worse than a thin one: it is a public statement that is not true, which is an FTC deception question as well as a carrier one. The machine-checkable half of it is easy — a policy that says \"we do not use cookies\" while an analytics tag loads on every page — and that half is usually a generated document nobody reconciled with the site.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body vs observed site behaviour",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Read the policy against what the site loads and what the programme does, and correct whichever is wrong. Done when every practice the document describes is one you can point at.",
      "notes": "Only partly decidable: a crawl can catch a cookie claim contradicted by a tracker, and nothing we fetch can confirm a retention period or an access control. The generation-side twin is POL-250; full verification is POL-107, which is a human question.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-109",
      "slug": "pol-109",
      "title": "A cart-reminder programme must say how abandonment is detected",
      "statement": "Where the programme sends shopping-cart reminders, the policy must state explicitly how cart abandonment is detected.",
      "rationale": "T-Mobile asks for this in almost these words, because a cart reminder is triggered by watching what someone did rather than by anything they asked for — so the policy has to say that cookies, a plugin or an account session are doing the watching. A retailer who switched an abandoned-cart flow on inside a platform has usually never seen the mechanism, let alone described it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "T-Mobile",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a sentence naming the mechanism — website cookies, a store plugin, a logged-in session — and what triggers the message. Done when the policy explains how the business knows a cart was left.",
      "example": "If you add items to your basket on acmecoffee.com and leave without checking out, a cookie on your browser lets us link that basket to your number so we can text you a reminder.",
      "pitfalls": [
        "The T&C has to reflect the cart programme as well (POL-110), and the opt-in disclosure has to name it (WEB-068). Fixing only the policy leaves two of the three."
      ],
      "notes": "Conditional on cart-reminder traffic, which no applicability dimension expresses — there is no cart-reminder attribute. The criteria pass immediately when nothing indicates the programme sends them. Severity divergence: T-Mobile and TCR grade it BLOCKING, CTIA SCMH Sev-2 MEDIUM. Strictest kept.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-110",
      "slug": "pol-110",
      "title": "A cart-reminder programme must be reflected in the terms",
      "statement": "Where the programme sends shopping-cart reminders, the SMS terms must describe them.",
      "rationale": "T-Mobile requires the terms to reflect the cart programme, and the reason is the same one behind the privacy-policy rule: a message triggered by watching what somebody did is not what \"promotional offers\" prepared them for. A retailer switching the flow on inside a platform changes what the programme sends without touching any document.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "T-Mobile",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add cart reminders to the message types the terms describe, and say how the number is linked to the basket. Done when the terms name them alongside whatever else the programme sends.",
      "example": "Acme Coffee Rewards sends promotional offers, rewards updates, and reminders when you leave something in your basket.",
      "pitfalls": [
        "The privacy policy owes the detection mechanism (POL-109) and the opt-in disclosure owes the message type (WEB-068). Fixing the terms alone leaves two of the three."
      ],
      "notes": "Conditional on cart-reminder traffic being in scope, which no applicability dimension expresses. The check reads the campaign copy for the signal and passes where it finds none.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-111",
      "slug": "pol-111",
      "title": "An incentivised opt-in needs a financial-incentive notice that agrees with the SMS clause",
      "statement": "Where joining the programme earns a discount or other reward, the policy must carry a CCPA Notice of Financial Incentive that does not contradict the messaging clause.",
      "rationale": "Offering ten per cent off for a phone number is a financial incentive under California law and needs its own notice, and the notice has to explain the value of the data — which is exactly the sentence that can undo a non-sharing promise if it is drafted carelessly. The offer is usually designed by marketing and the policy by someone else, so the two are rarely read together.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body + opt-in offer text",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the notice — what the incentive is, what data it relates to, how to withdraw, and a good-faith estimate of value — and state that messaging opt-in data is still never shared. Done when the two sections agree.",
      "pitfalls": [
        "The value estimate is the sentence that can contradict the non-sharing clause. Base it on the value of the customer relationship rather than on what the data would fetch if sold."
      ],
      "notes": "Conditional on the opt-in offering an incentive, which no applicability dimension expresses; the criteria pass immediately where none is offered. The terms-side twin is POL-234.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-112",
      "slug": "pol-112",
      "title": "Location-triggered messaging must be described in the policy",
      "statement": "The privacy policy must describe how location data is collected and why, where messaging is triggered by location.",
      "rationale": "A message that arrives because someone walked past a shop is the kind of thing consumers find startling, and the policy is the only place they can find out it was going to happen. Location tracking is usually switched on inside a marketing platform rather than built deliberately, so the practice exists long before anyone thinks to document it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Klaviyo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the collection method, the messaging use and the opt-out in the messaging section. Done when someone who receives a location-triggered message can find out why in the policy.",
      "example": "If you enable location in the Acme Coffee app we use it to text you when you are near a store with an offer on. Turn location off in the app to stop those texts without leaving the programme.",
      "notes": "Conditional on the programme being location-triggered, which no applicability dimension expresses; the criteria pass immediately where nothing in the registration indicates location triggering.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-113",
      "slug": "pol-113",
      "title": "The policy needs a children's-data section",
      "statement": "The privacy policy must carry a children's-data section with a parental-consent route and a deletion route.",
      "rationale": "A messaging programme cannot tell how old the person holding the phone is, so the policy has to say what happens when a child ends up in it — and the FTC's amended rule raises the bar on what that section must contain. Ordinary businesses are in scope more often than they expect, because the question is whether children are reachable rather than whether they are the audience.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "FTC",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a children's section giving the age position, the parental route and the deletion commitment. Done when a parent reading it knows exactly who to contact and what will happen.",
      "example": "The Acme Coffee text programme is not directed at children under 13 and we do not knowingly collect their information. A parent can email privacy@acmecoffee.com to see what we hold about a child and we will delete it.",
      "notes": "The FTC amended rule took effect on 2025-06-23 with full compliance required from 2026-04-22, so this is live rather than forthcoming.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-114",
      "slug": "pol-114",
      "title": "A child-directed service owes the full COPPA notice",
      "statement": "Where the service is directed at children, the policy must carry the COPPA online-notice elements, including the categories of third parties that receive children's data and the purpose.",
      "rationale": "A general \"we do not target children\" section is not a COPPA notice, and a service that is child-directed needs the full one — including the third-party disclosure, which is precisely what the messaging non-sharing clause has to be reconciled with. The FTC enforces this directly, so it is the one place in this layer where the regulator rather than the carrier is the risk.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy children's section",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "FTC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the COPPA online notice: what is collected from children, how it is used, every category of third party that receives it and why, the parental-consent mechanism, and the parental review and deletion routes. Done when each element is present and specific.",
      "pitfalls": [
        "The third-party categories element and the SMS non-sharing clause have to agree. If the notice lists recipients of children's data, exclude messaging opt-in data from that list explicitly, or the two contradict."
      ],
      "notes": "Conditional on the service being child-directed, which no applicability dimension expresses. The criteria pass immediately where nothing in the registration indicates a child-directed service, and record the condition rather than leaving the rule looking universal.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-115",
      "slug": "pol-115",
      "title": "Children's data needs a published retention policy",
      "statement": "The privacy policy must publish a written data-retention policy for children's data, with the purpose, the justification and the deletion timeframe.",
      "rationale": "The 2025 COPPA amendments require the retention policy to be published rather than merely held, which is a change most businesses have not noticed. The substance matters as much as the publication: a retention rule with no deletion timeframe is a commitment to keep a child's data indefinitely, stated in public.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "FTC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the retention rule to the children's section: what is kept, why, and for how long. Done when the section states a period rather than a condition.",
      "example": "Where we learn that a subscriber is under 13 we delete the number and its consent record within 30 days, keeping only the fact of the deletion.",
      "notes": "Conditional on children's data being in scope, which no applicability dimension expresses; the criteria pass immediately where it is not.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-116",
      "slug": "pol-116",
      "title": "A written internal do-not-call policy must exist",
      "statement": "The business must maintain a written internal do-not-call policy and make it available on demand.",
      "rationale": "The FCC requires the written policy from anyone doing telemarketing-adjacent outreach, and \"available on demand\" means a consumer or a regulator can ask for it — so its absence surfaces at the worst possible moment. It is an internal document rather than a published one, which is why businesses that have every public policy in order still do not have this one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "internal do-not-call policy document",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the internal policy — who maintains the list, how a request is recorded, how long entries are kept, how staff are trained — and be able to send it to anyone who asks. Done when a copy exists and someone owns it.",
      "notes": "Nothing in the registration reveals whether this document exists. The user has to confirm they hold one, and to know who would produce it if a consumer or the FCC asked. Where it is published on the site, a crawl can find it; where it is not, only the business knows.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does a written internal do-not-call policy exist, and do you know who would produce it if a consumer or the FCC asked?",
        "howToCheck": [
          "Find the document. It is internal rather than published, which is why businesses with every public policy in order still do not have this one.",
          "Confirm it covers who maintains the list, how a request is recorded, how long entries are kept, and how staff are trained.",
          "Name the person who owns it."
        ],
        "failureLooksLike": "\"Available on demand\" means the absence surfaces at the worst possible moment — when a consumer or a regulator has already asked for it."
      }
    },
    {
      "id": "POL-117",
      "slug": "pol-117",
      "title": "No document may designate an exclusive way to revoke consent",
      "statement": "No policy, terms or message copy may state that a single named method is the only way to opt out.",
      "rationale": "The FCC settled in 2024 that a consumer may revoke consent by any reasonable means, so a document naming one exclusive route is asserting a restriction the rule removes — and the consumer who emails instead of texting STOP, and keeps receiving messages, has a complaint the brand cannot answer. The language is usually written to be helpful: \"to unsubscribe, reply STOP\" becomes \"STOP is the only way to unsubscribe\" in an effort to be clear.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + SMS terms body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "FCC"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any wording that makes one route exclusive and say the opposite: name the easy route and confirm that other reasonable requests are honoured too. Done when no sentence in either document contains \"only way\", \"must be submitted through\", or an equivalent.",
      "example": "Reply STOP to any message to unsubscribe. You can also email support@acmecoffee.com or call 415-555-0134 — we honour any reasonable request to stop.",
      "pitfalls": [
        "The exclusivity often lives in a portal or account-settings sentence rather than in the SMS section: \"opt-out requests must be submitted through your account\" is the same defect wearing different clothes."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-121",
      "slug": "pol-121",
      "title": "The policy must say what it covers",
      "statement": "The privacy policy must state its scope — which sites, apps and offline channels it applies to.",
      "rationale": "A policy scoped to \"this app\" while the opt-in happens on the website is the wrong document for the programme being registered, and a reviewer reads the scope line before anything else. Businesses inherit the mismatch from whichever product the policy was first written for.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open the policy with a scope sentence naming every surface it covers, including the messaging programme. Done when the document plainly covers the surface where consent is collected.",
      "example": "This policy covers acmecoffee.com, the Acme Coffee mobile app, our stores, and the Acme Coffee text messaging programme.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-122",
      "slug": "pol-122",
      "title": "The policy must list the categories of personal information collected",
      "statement": "The privacy policy must list the categories of personal information collected, using the statutory category names.",
      "rationale": "California requires the statutory names rather than a plain-English list, and the same section answers Twilio's demand to know what customer data is collected — so it is one of the few CCPA elements that carries direct 10DLC weight. It is also the section that most often contradicts the messaging clause, because \"identifiers\" includes the phone number.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the statutory categories that apply and give an example of each in plain words alongside. Done when a reader sees both the legal category and what it means for them.",
      "example": "Identifiers — including your name, email address and mobile phone number.",
      "pitfalls": [
        "This list feeds the sold-or-shared table two sections later. Whatever appears here must be excluded there for messaging data, or POL-066 fires on the contradiction."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-123",
      "slug": "pol-123",
      "title": "The policy must list where the information came from",
      "statement": "The privacy policy must list the sources of the personal information it collects.",
      "rationale": "The sources list is where a policy either confirms or quietly contradicts the claim that every number was given directly by its owner — a list naming data brokers or partners tells a reviewer the opposite of what the messaging section promises. That makes a nominally low-relevance CCPA element load-bearing for a messaging registration.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the sources honestly and make sure none of them contradicts the messaging section. Done when the sources list and the opt-in description tell the same story.",
      "example": "We collect personal information directly from you — at checkout, in your account, and when you contact us.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-124",
      "slug": "pol-124",
      "title": "The policy must state the purpose of each category",
      "statement": "The privacy policy must state the business or commercial purpose for each category of information, in terms a consumer can understand.",
      "rationale": "The statute asks for a meaningful understanding rather than a list, which is a deliberate rejection of the \"to improve our services\" formulation that says nothing. For a messaging programme it is also the section where the purpose of holding the number is either stated plainly or lost in a generic paragraph.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give each category its own purpose, in the words a customer would use. Done when a reader can tell why you hold each thing you listed.",
      "example": "Identifiers — to fulfil your orders, to answer support requests, and to send the text messages you asked for.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-125",
      "slug": "pol-125",
      "title": "The policy must list who the information is disclosed to",
      "statement": "The privacy policy must list the categories of third parties personal information is disclosed, sold or shared to, and why.",
      "rationale": "This is the section carrier reviewers grep hardest, because it is where a policy admits what actually happens to the data — the research calls it the section that trips Bandwidth 7103. A careful CCPA disclosure and a compliant messaging clause pull in opposite directions here, and reconciling them is the whole job.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the recipient categories accurately and add the messaging exclusion immediately after the list. Done when the section is both a truthful CCPA disclosure and consistent with the non-sharing clause.",
      "example": "We disclose personal information to our payment processor, our fulfilment partner and our messaging vendor, each acting on our behalf. All of the above exclude text messaging originator opt-in data and consent.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-126",
      "slug": "pol-126",
      "title": "The policy must say whether data was sold or shared in the last 12 months",
      "statement": "The privacy policy must state whether personal information was sold or shared in the preceding twelve months, or affirmatively that it was not.",
      "rationale": "The statute wants a yes or a no, and the answer is read by carriers as a statement about the business model rather than as a compliance artefact. A business that says yes for its advertising data and never carves out the messaging data has contradicted its own non-sharing clause in the most explicit way available.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the answer plainly and, where it is yes, exclude messaging opt-in data from it in the same paragraph. Done when the twelve-month statement cannot be read as covering the text programme.",
      "example": "In the last 12 months we have not sold or shared personal information. Text messaging opt-in data and consent are never sold or shared in any case.",
      "notes": "Must not contradict the non-sharing clause; where it does, POL-066 reports the contradiction.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-127",
      "slug": "pol-127",
      "title": "The policy must state its position on minors under 16",
      "statement": "The privacy policy must state whether the business has actual knowledge that it sells or shares the personal information of consumers under 16.",
      "rationale": "California treats under-16 data as requiring opt-in rather than opt-out, so the statement is the business declaring which regime it is in. For a messaging programme the answer should be trivially no, and saying so removes a question a reviewer would otherwise have to resolve from silence.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence to the CCPA section. Done when the document answers the question rather than leaving it to inference.",
      "example": "We do not have actual knowledge that we sell or share the personal information of consumers under 16.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-128",
      "slug": "pol-128",
      "title": "The policy must state its position on sensitive personal information",
      "statement": "The privacy policy must state whether sensitive personal information is used beyond the permitted purposes.",
      "rationale": "The statement decides whether the business owes a Limit-the-Use link, so getting it wrong adds an obligation or drops one. Messaging programmes rarely touch sensitive information at all, which makes the honest answer short — and its absence a gap a reviewer has to fill by guessing.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence, and where the answer is no, say so plainly rather than omitting the section. Done when the document states the position either way.",
      "example": "We do not use or disclose sensitive personal information beyond the purposes permitted under California law.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-129",
      "slug": "pol-129",
      "title": "The policy must name or categorise its processors",
      "statement": "The privacy policy must name or categorise the third-party processors that handle personal information.",
      "rationale": "The service-provider carve-back the messaging clause depends on is only meaningful if the reader can tell who the service providers are — otherwise \"vendors acting on our behalf\" is an open door with a friendly label. Naming the messaging platform and the carriers is also the sentence that makes the carve-back obviously narrow rather than obviously convenient.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "7109",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the processor categories, naming the messaging vendor explicitly. Done when the carve-back in the messaging clause points at a list a reader can actually see.",
      "example": "Our processors are: our messaging platform and the wireless carriers that deliver the texts, our hosting provider, our payment processor, and our analytics provider.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-130",
      "slug": "pol-130",
      "title": "The policy must disclose cookies, pixels and tracking",
      "statement": "The privacy policy must disclose the cookies, pixels, SDKs and tracking it uses, and how to opt out of them.",
      "rationale": "For most brands this is ordinary privacy law with no messaging consequence — except where cart reminders are in scope, when the cookie section is what explains how the message was triggered at all. It is also the section most often contradicted by the site itself, which is what POL-108 catches.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the tracking the site actually loads and give the route to turn it off. Done when the section matches what a browser sees on the home page.",
      "example": "We use cookies to keep your basket, to measure how the site is used, and — if you have joined the text programme — to tell when a basket was left unfinished. You can manage them from the cookie settings link in the footer.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-131",
      "slug": "pol-131",
      "title": "The policy must describe how opt-out preference signals are handled",
      "statement": "The privacy policy must describe how it honours browser opt-out preference signals such as Global Privacy Control.",
      "rationale": "California requires the signal to be honoured, and the disclosure is how a consumer who has set it knows whether it reached anyone. It carries no direct 10DLC weight, and it sits beside the sections that do — so the cost of adding it is a sentence and the cost of leaving it out is a gap in a document reviewers read end to end.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a sentence stating that the signal is recognised and what it does. Done when a reader who has set GPC knows what your site does about it.",
      "example": "We recognise the Global Privacy Control signal and treat it as a request to stop sharing your personal information.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-133",
      "slug": "pol-133",
      "title": "A brand handling EU or UK data needs a GDPR addendum",
      "statement": "The privacy policy must carry a GDPR addendum naming the controller, the legal bases, transfers, withdrawal of consent and the right to complain.",
      "rationale": "A US messaging programme picks up EU or UK subjects the moment it has customers who travel or move, and the addendum is what makes the same document work for them. The withdrawal-of-consent element is the one that overlaps with messaging directly: it has to describe the same route the SMS terms describe, or the two documents disagree about how to leave.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "GDPR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the addendum as its own section rather than weaving it through the document, and make its consent-withdrawal route the same one the SMS terms give. Done when each element is present and the two documents agree.",
      "example": "For visitors in the EU and UK: the controller is Acme Coffee Co, LLC. We rely on your consent to send marketing texts; you can withdraw it at any time by replying STOP or emailing privacy@acmecoffee.com, and you may complain to your local supervisory authority.",
      "notes": "Absorbs POL-132, which states the legal-bases element on its own — one element of the addendum rather than a separate obligation. Conditional on the brand processing EU or UK data, which no applicability dimension expresses; the criteria pass immediately where nothing indicates it.",
      "catalogIds": [
        "POL-132"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-134",
      "slug": "pol-134",
      "title": "The policy must enumerate the CCPA consumer rights",
      "statement": "The privacy policy must enumerate the CCPA and CPRA consumer rights.",
      "rationale": "The rights section is what turns a policy from a description into something a consumer can act on, and California requires the specific list rather than a summary. For a messaging programme it also carries the deletion right, which is the one people exercise after they unsubscribe and find they are still on file.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List all six rights explicitly. Done when each appears by name rather than being implied by a general statement.",
      "example": "You have the right to know what we hold, to delete it, to correct it, to opt out of its sale or sharing, to limit our use of sensitive information, and not to be treated differently for exercising any of these.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-135",
      "slug": "pol-135",
      "title": "The policy must say how to exercise those rights",
      "statement": "The privacy policy must give instructions for exercising consumer rights, including submission methods, verification and authorised agents.",
      "rationale": "A list of rights with no route to exercise them is the commonest way a policy looks compliant and does nothing — the consumer knows what they are entitled to and not who to ask. The verification step in particular has to be described in advance, because a business that invents an identity check when a request arrives looks like it is refusing.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the submission routes, the verification step and the agent route in the rights section itself. Done when a reader can start a request from the policy without hunting.",
      "example": "To make a request, email privacy@acmecoffee.com or call 415-555-0134. We will ask for the email address or mobile number on your account to verify it is you. An authorised agent may act for you with your written permission.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-136",
      "slug": "pol-136",
      "title": "A business that sells or shares must publish the Do-Not-Sell link",
      "statement": "Where the business sells or shares personal information, the \"Do Not Sell or Share My Personal Information\" link must be published on the site.",
      "rationale": "California requires the link wherever the business does either, and its absence is a compliance failure in its own right. It is worth pairing with the messaging carve-out deliberately rather than by accident: publishing the link invites a reviewer to ask whether the messaging data is inside the request, which is the question POL-067 exists to answer in advance.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "site-wide Do-Not-Sell link",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CCPA"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the link in the site footer and state next to it that messaging opt-in data is excluded from sale or sharing in any case. Done when the link is present and the exclusion is unambiguous.",
      "notes": "Conditional on the business selling or sharing personal information, which no applicability dimension expresses; the criteria pass immediately where the policy discloses neither. Pair with POL-067 — publishing the link without the carve-out statement is the trap.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-137",
      "slug": "pol-137",
      "title": "Other state-law addenda are needed where thresholds are met",
      "statement": "Where the business meets the applicability threshold of another US state privacy statute, the policy must carry that state's addendum.",
      "rationale": "A dozen states now have their own thresholds based on resident counts and revenue, and crossing one is invisible from inside the business — nobody gets a letter. The addenda are close enough to each other that adding them is cheap, and the cost of missing one falls on a consumer whose rights simply do not appear in the document.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy state addenda",
      "severity": "LOW",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "State law"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Count residents by state against each statute's threshold once a year, and add the addendum for any state you have crossed. Done when the document covers every regime the business is actually in.",
      "notes": "Deciding this needs a count of consumers by state and a revenue figure, neither of which the registration carries and neither of which we can look up. The user has to check their own numbers against the Virginia, Colorado, Connecticut, Utah, Texas, Oregon, Montana and Delaware thresholds; the catalog records the 10DLC relevance as none, so this is a legal exposure rather than a registration risk.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you counted residents by state and revenue against each state privacy statute's threshold in the last year?",
        "howToCheck": [
          "Pull the numbers: consumers by state, and annual revenue.",
          "Compare against the Virginia, Colorado, Connecticut, Utah, Texas, Oregon, Montana and Delaware thresholds.",
          "Add the addendum for any state you have crossed. They are close enough to each other that adding them is cheap."
        ],
        "failureLooksLike": "Crossing a threshold is invisible from inside the business — nobody gets a letter. The cost falls on a consumer whose rights simply do not appear in the document."
      }
    },
    {
      "id": "POL-138",
      "slug": "pol-138",
      "title": "The policy should offer an accessible alternative format",
      "statement": "The privacy policy must carry an accessibility statement or a route to an alternative format.",
      "rationale": "A consumer who cannot read the page cannot exercise any right in it, and California expects a route for people with disabilities. It costs one line and it is the kind of omission that reads badly next to a document otherwise full of rights.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a line offering the policy in an alternative format on request, with a contact route. Done when the document says how to get it in another form.",
      "example": "Need this policy in another format? Email support@acmecoffee.com or call 415-555-0134 and we will provide one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-139",
      "slug": "pol-139",
      "title": "The policy should be printable",
      "statement": "The privacy policy should be available in a form a consumer can print or save.",
      "rationale": "A policy that exists only as an expanding accordion, or that prints as a single blank page, cannot be kept by the person it governs — and California asks for a printable format for exactly that reason. It is a rendering accident rather than a decision, so nobody discovers it until somebody tries.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy page rendering",
      "severity": "LOW",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Render the full text in the page rather than behind toggles, and add a print stylesheet or a downloadable copy. Done when printing the page produces the whole document.",
      "notes": "Only partly decidable from a text crawl: a fetch that returns the full body is good evidence the text is in the page, and it says nothing about how the page prints. The judge should report which of the two it could see.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-140",
      "slug": "pol-140",
      "title": "Both documents must say how changes are communicated",
      "statement": "The privacy policy must carry a section describing how changes to it are communicated.",
      "rationale": "A policy that can change silently is a promise with an expiry date nobody is told about, and the section is what lets a consumer know a change has happened without re-reading the document. It matters more for a messaging programme than for most, because a material change to the programme is supposed to be reflected in these documents and dated.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a \"Changes to this policy\" section to both the privacy policy and the terms, saying how notice is given and when a change takes effect. Done when both documents carry it.",
      "example": "We may update this policy. We will change the Last updated date above and, where the change is material, post a notice on acmecoffee.com for 30 days before it takes effect.",
      "notes": "Absorbs POL-218, the same clause required of the terms — one requirement across both documents rather than two.",
      "catalogIds": [
        "POL-218"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-141",
      "slug": "pol-141",
      "title": "The published documents must show when they were last updated",
      "statement": "The privacy policy and the terms must each display the date they were last updated.",
      "rationale": "The date is how a reviewer, and a consumer, tell a maintained document from an abandoned one — CTIA asks for up-to-date, accurate programme information and CCPA requires the date outright. It also settles an argument later: when a programme changes and somebody asks what the terms said at the time, an undated document cannot answer.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms date block",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CCPA",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a \"Last updated\" line carrying a full date at the top or bottom of each document, and change it whenever you change the text. Done when both pages show a date a reader can compare against today.",
      "example": "Last updated: 1 July 2026",
      "pitfalls": [
        "A date generated from the page build changes every deploy and tells a reader nothing about the text. Set it by hand when the wording changes."
      ],
      "notes": "Absorbs POL-142 (a distinct effective date where the two differ), POL-159 (dating the SMS terms) and POL-160 (dating the ToS) — one dating requirement across all three documents. Whether the date is recent enough for the programme it describes is POL-233.",
      "catalogIds": [
        "POL-142",
        "POL-159",
        "POL-160"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-150",
      "slug": "pol-150",
      "title": "The SMS terms must exist as a document of their own",
      "statement": "The SMS terms must be published as a distinct, directly accessible document rather than only as a clause inside the general website terms.",
      "rationale": "A consumer who wants to know what they signed up for should not have to read a commercial terms-of-sale document to find it, and a reviewer given a general ToS has to hunt for the messaging clauses and decide whether what they found is the programme. Both problems disappear with a page whose whole subject is the text programme, which is why most providers ask for one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms document",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Bandwidth",
        "TCR",
        "Sinch",
        "Plivo",
        "Twilio",
        "Klaviyo",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "TFV 1504",
          "remediable": true
        },
        {
          "provider": "Twilio",
          "code": "30882",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish a dedicated SMS terms page — /sms-terms is the conventional path — and register that URL on the campaign, keeping a cross-referencing section in the general ToS. Done when the terms URL opens a page whose subject is the messaging programme.",
      "pitfalls": [
        "Registering the general ToS URL and relying on a reader scrolling to the SMS section satisfies POL-211 and fails this rule. The two are kept separate because providers genuinely differ, so the safe build is a dedicated page plus an anchored section."
      ],
      "notes": "Contested in the catalog: no primary source mandates a dedicated page over an anchored ToS section, and the canonical position taken here is to generate both. POL-211 is the relaxed form — an anchored section satisfies that and fails this.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-151",
      "slug": "pol-151",
      "title": "The SMS terms must be about the messaging programme",
      "statement": "The SMS terms must cover the messaging programme specifically rather than restating general website terms.",
      "rationale": "A page titled \"SMS Terms\" that turns out to be the standard site terms with a heading changed tells a reviewer that nobody wrote terms for this programme — and the consumer still has no idea what they will receive. It happens when the terms page is produced to satisfy the registration field rather than to document anything.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio",
        "TCR",
        "Infobip",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30882",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Write the terms around this programme: what it sends, how often, who it is for, what it costs, how to stop and how to get help. Done when nothing in the document would be true of a different business.",
      "example": "Acme Coffee Rewards sends weekly promotional offers and rewards balance updates to customers who joined at checkout. Message frequency varies. Message and data rates may apply.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-152",
      "slug": "pol-152",
      "title": "The SMS terms must be findable rather than buried",
      "statement": "The SMS terms must be near the top of the document or reachable by their own anchor, not buried inside terms about other services.",
      "rationale": "CTIA and T-Mobile both treat consent details buried in unrelated terms as a consent defect rather than a formatting one, because a consumer who cannot find the terms has not been told what they are agreeing to. The usual shape is a fifty-clause commercial ToS with the messaging paragraph at clause 34, which is compliant on a word count and useless to a reader.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms section position and anchor",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM",
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "T-Mobile",
        "Twilio",
        "Bandwidth",
        "AWS",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the messaging section its own anchor and link that anchor from the opt-in, or publish the terms as their own page. Done when the registered link lands the reader on the messaging terms rather than at the top of a long document.",
      "example": "terms_and_conditions_url: https://acmecoffee.com/terms#sms — the link lands on the messaging section itself.",
      "notes": "Absorbs POL-153, which states the same findability requirement as a prohibition on burying opt-in details in unrelated terms. Severity divergence in the catalog: CTIA, Telnyx and Bandwidth grade the buried-consent form BLOCKING; that stricter form lives in the consent-surface layer, and this is the document-side variant, kept at HIGH.",
      "catalogIds": [
        "POL-153"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-156",
      "slug": "pol-156",
      "title": "Published policy must not contain unreplaced template tokens",
      "statement": "The live privacy policy and terms must contain no {curly brace} or {{PLACEHOLDER}} tokens left from a template.",
      "rationale": "An unreplaced token proves the document was generated and never finished, which invalidates the whole page in a reviewer's eyes — if the company name is still a placeholder, no promise in the document is attributable to anyone. It is trivially detectable and embarrassingly common.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy body + terms body",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Search the published pages for \"{\" and \"[\" and replace every remaining token with real values, then re-publish.",
      "example": "Replace \"{{COMPANY_NAME}}\" with \"Acme Coffee Co, LLC\" and \"[EFFECTIVE DATE]\" with a real date.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-157",
      "slug": "pol-157",
      "title": "The documents must describe the programme as it runs today",
      "statement": "The terms and the privacy policy must give up-to-date, accurate information about the programme's details and functionality.",
      "rationale": "CTIA asks for accuracy rather than mere presence, and a document describing a programme that has since changed is worse than a missing one — a consumer relying on a frequency cap that no longer holds has been misled by a compliance document. Programmes drift constantly: a new message category, a different keyword set, a platform migration, and the terms are rarely part of the change.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Read both documents against the campaign as registered and correct anything that has moved on — categories, frequency, keywords, contact routes. Done when every factual claim in either document is true of the programme today.",
      "example": "Terms updated alongside the campaign: \"up to 6 messages per month\" replaced by \"message frequency varies\" when the weekly send was added.",
      "notes": "Whether the documents carry a date is POL-141; whether the date is recent enough for the programme is POL-233. This rule is about the substance rather than the dating.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-170",
      "slug": "pol-170",
      "title": "Terms must contain a dedicated SMS/messaging section",
      "statement": "The terms of service must contain a section covering the messaging programme, or a dedicated SMS terms page must exist.",
      "rationale": "The terms are where the programme is described in full: what it sends, how often, what it costs, and how to leave. A general commercial ToS with no messaging section leaves a reviewer unable to confirm the programme exists as described anywhere the consumer can read it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add an \"SMS / Text Messaging Terms\" section covering programme description, frequency, rates, STOP and HELP keywords, carrier non-liability, and a support contact. A separate dedicated SMS terms page is equally acceptable and often clearer.",
      "catalogIds": [
        "POL-211"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-171",
      "slug": "pol-171",
      "title": "The terms must describe the programme",
      "statement": "The SMS terms must carry a description of the programme — what the messages are about.",
      "rationale": "Every other element in the terms is a parameter of a programme the reader has to already understand, so without the description the frequency, the rates and the keywords are attached to nothing. It is the single element most consistently required across providers, and the easiest to leave out because the author knows what the programme is.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "Telnyx",
        "Sinch",
        "AWS",
        "TCR",
        "Bandwidth",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open the terms with a sentence or two naming the programme and saying what it sends and to whom. Done when a stranger reading only the first paragraph knows what they would receive.",
      "example": "Acme Coffee Rewards is a text messaging programme for Acme Coffee customers. We send promotional offers, seasonal sale notifications and rewards balance updates.",
      "notes": "The research's own validator uses a floor of roughly fifteen words for the description, which is a useful sanity check rather than a published threshold.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-175",
      "slug": "pol-175",
      "title": "The terms must carry opt-out information",
      "statement": "The SMS terms must state how to stop the messages — \"Reply STOP to cancel\" or a clear equivalent.",
      "rationale": "This is the one element every provider screens for mechanically, because it is the consumer's exit and the thing a complaint is measured against. Terms drafted as a legal document rather than as instructions often describe the right to withdraw consent without naming the keyword, which reads as compliant to the author and fails the string match.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "AWS",
        "Bandwidth",
        "Sinch",
        "Telnyx",
        "TCR",
        "Infobip",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30908",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the keyword sentence to the terms in plain words. Done when the document contains the word STOP as an instruction rather than a description of a right.",
      "example": "To stop receiving messages, reply STOP to any message from us.",
      "pitfalls": [
        "Naming STOP as the only route fails POL-117. Give the keyword and say other reasonable requests are honoured as well."
      ],
      "notes": "The catalog exempts single-message programmes and lets T-Mobile accept the opt-out in the advertisement instead of the terms for print CTAs. Neither exemption is expressible as a tag we hold, and both are narrower than the registrations this registry is aimed at, so the rule fires universally.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-176",
      "slug": "pol-176",
      "title": "SMS terms must state message frequency and that rates may apply",
      "statement": "The SMS terms section must disclose message frequency and that message and data rates may apply.",
      "rationale": "These are the same two disclosures required at the point of opt-in, repeated where a consumer can find them later. A reviewer checking consistency between the opt-in and the terms will treat their absence here as evidence the programme is not documented.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add both statements to the SMS terms section, and make the frequency claim match what you tell people at the opt-in — a reviewer comparing the two will notice if one says \"varies\" and the other promises four a month.",
      "example": "Message frequency varies. Message and data rates may apply.",
      "catalogIds": [
        "POL-172",
        "POL-173"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-177",
      "slug": "pol-177",
      "title": "The terms must say one final message follows an opt-out",
      "statement": "The SMS terms must state that a single confirmation message follows an opt-out and that nothing further is sent.",
      "rationale": "CTIA expects exactly one confirmation after STOP, and a subscriber who receives it without warning reads it as the opt-out having failed — which is the most common trigger for a complaint filed immediately after someone has successfully unsubscribed. One sentence in the terms turns that message from evidence of a broken system into evidence of a working one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms opt-out section",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence next to the opt-out instruction. Done when the terms set the expectation that one confirmation arrives and nothing after it.",
      "example": "After you reply STOP we will send one confirmation message, and then no further messages.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-178",
      "slug": "pol-178",
      "title": "The terms should say how to re-subscribe",
      "statement": "The SMS terms should describe how someone can rejoin the programme after opting out.",
      "rationale": "People opt out during a busy period and want back in later, and without a documented route the only options are to guess a keyword or to contact support — so the business loses a subscriber who wanted to return. It also prevents a worse outcome: a business re-adding a number without fresh consent because the customer asked verbally and nobody recorded it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms opt-out section",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the re-subscription route beside the opt-out instruction — a keyword, the original sign-up page, or a support contact. Done when the terms answer \"how do I come back?\".",
      "example": "Changed your mind? Reply START to rejoin, or sign up again at acmecoffee.com/rewards.",
      "pitfalls": [
        "A re-subscription route is a fresh opt-in and has to collect fresh consent with the full disclosure. Do not describe it as \"we will just turn you back on\"."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-180",
      "slug": "pol-180",
      "title": "SMS terms must state consent is not a condition of purchase",
      "statement": "The SMS terms must contain the statement that consent is not a condition of any purchase.",
      "rationale": "The same TCPA element required at the opt-in is required in the terms, so the promise survives the consumer forgetting what the checkout page said. Reviewers check both places, and the terms are the easier of the two to forget.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "FCC",
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence to the SMS terms section, worded as an unconditional statement. Check it is actually true of your checkout before publishing it, because the claim is verified against the live form by a separate rule.",
      "example": "Consent to receive text messages is not a condition of any purchase.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-181",
      "slug": "pol-181",
      "title": "The terms must describe every opt-in method actually used",
      "statement": "The SMS terms must describe every way people join the programme, consistently with the registered message flow.",
      "rationale": "Vonage asks for all of them rather than the primary one, because a subscriber who joined at a counter and reads terms describing only a web form cannot tell whether the document applies to them. Programmes accumulate entry points over time — a keyword added for an event, a tick box added at checkout — and the terms are written once.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body vs campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio",
        "CTIA",
        "Vonage"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List every entry point in the terms — the checkout box, the keyword, the paper form, the phone call — and keep the list the same as the message flow. Done when the two documents name the same set.",
      "example": "You can join Acme Coffee Rewards by ticking the box at checkout on acmecoffee.com, or by texting JOIN to 55512.",
      "notes": "Three rules read the same fact from three sources and can disagree independently: POL-091 asks the privacy policy, this one asks the terms against the declared flow, and POL-232 asks the terms against the surfaces found by crawling the site.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-182",
      "slug": "pol-182",
      "title": "SMS terms must carry a carrier non-liability disclaimer",
      "statement": "The SMS terms must state that wireless carriers are not liable for delayed or undelivered messages.",
      "rationale": "Carriers require this disclaimer as a condition of carrying A2P traffic — it is the clause that stops them being sued when a message arrives late. Its absence is a standard, mechanically-checked omission in SMS terms review.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the standard carrier disclaimer to the SMS terms section, naming the carriers rather than only limiting your own liability. Done when the terms say delivery is not guaranteed and that the carriers are not answerable for it.",
      "example": "Delivery is not guaranteed. Neither Acme Coffee nor the wireless carriers are liable for delayed or undelivered messages.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-184",
      "slug": "pol-184",
      "title": "SMS terms must list the supported opt-out and help keywords",
      "statement": "The SMS terms must name the keywords the programme honours for opting out and getting help.",
      "rationale": "The terms are where a consumer looks up how to leave when the last message they received has scrolled away. The keywords listed here must match the keywords the platform actually honours, which is the registration-side twin of a runtime obligation.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "List the keywords in the SMS terms, and make the list identical to the keywords declared on the campaign — a mismatch between the two is itself a finding.",
      "example": "To stop receiving messages, reply STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, or UNSUBSCRIBE. For help, reply HELP.",
      "catalogIds": [
        "POL-176",
        "POL-179"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-185",
      "slug": "pol-185",
      "title": "The terms should state a minimum age",
      "statement": "The SMS terms should state the eligibility and minimum age for the programme, consistently with the privacy policy.",
      "rationale": "The age line is what makes the children's section of the privacy policy operative for the messaging programme rather than for the website in general, and it is the only place a subscriber is told whether they are eligible at all. Where the two documents give different ages the pair contradicts itself, which is the defect POL-231 catches.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms eligibility clause",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the minimum age in the terms and make it agree with the privacy policy's children's section. Done when both documents give the same number.",
      "example": "You must be 18 or older, or 13 or older with a parent's permission, to join Acme Coffee Rewards.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-186",
      "slug": "pol-186",
      "title": "The terms should say the subscriber warrants the number is theirs",
      "statement": "The SMS terms should state that the subscriber warrants the number is their own and will tell the brand if it changes or is deactivated.",
      "rationale": "Numbers are recycled constantly, so a consented number becomes a stranger's number without anybody doing anything wrong — and the stranger receives marketing they never asked for. The clause is what makes the carrier deactivation files the brand is expected to check into a shared responsibility rather than a one-sided cleanup.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "Klaviyo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the warranty and the notification duty to the terms, and pair it with checking deactivation data on your side. Done when the terms say the number must belong to the person who gave it.",
      "example": "By joining you confirm the mobile number you gave us is yours. Please tell us if it changes or you stop using it, so we do not text someone else.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-187",
      "slug": "pol-187",
      "title": "The terms should state the right to change or end the programme",
      "statement": "The SMS terms should state that the brand may change or terminate the programme, and how notice will be given.",
      "rationale": "Programmes end, and a subscriber who simply stops hearing from one has no way to tell whether it closed or whether their opt-out was mishandled. The notice half is what matters: a right reserved with no notice route reads as the business being able to change the deal silently, which is the reading a reviewer takes.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Klaviyo"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the right and the notice route together. Done when the terms say both that the programme may change and how you would tell people.",
      "example": "We may change or end the Acme Coffee Rewards text programme at any time. We will post any change on acmecoffee.com/sms-terms and text subscribers before it takes effect.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-188",
      "slug": "pol-188",
      "title": "SMS terms and privacy policy must link to each other",
      "statement": "The SMS terms must link to the privacy policy, and the privacy policy should link back to the SMS terms.",
      "rationale": "A consumer reading either document should be able to reach the other, and reviewers follow those links to confirm the two belong to the same programme. Orphaned documents also tend to drift apart in content, which produces the contradictions the inverse-trap rules catch.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body + privacy policy body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a link from the SMS terms to the privacy policy and a link back the other way, so a consumer landing on either document can reach the other and a reviewer can confirm the two belong to the same programme.",
      "example": "See our Privacy Policy at https://acmecoffee.com/privacy for how we handle your information.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-189",
      "slug": "pol-189",
      "title": "The terms must distinguish transactional from promotional messages",
      "statement": "Where the programme sends both transactional and promotional messages, the terms must distinguish them and describe the separate consent.",
      "rationale": "The two categories carry different consent standards, and a subscriber who agreed to order updates has not agreed to weekly offers — bundling them is the defect Twilio codes at the campaign level. A document that lumps them together makes the bundling official, which is worse for the brand than the practice alone.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30913",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the two categories separately in the terms and say that promotional messages need their own opt-in. Done when a reader can tell which consent covers which messages.",
      "example": "Order updates are sent to every customer who places an order. Promotional offers are sent only to customers who ticked the marketing box — you can receive one without the other.",
      "notes": "Applies only where both categories are sent; the criteria pass immediately for a single-category programme, since there is nothing to distinguish.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-190",
      "slug": "pol-190",
      "title": "SMS terms must give a working support contact",
      "statement": "The SMS terms must provide a support email or phone number a consumer can actually reach.",
      "rationale": "CTIA requires help routes not to be dead ends: the contact in your terms must lead to a human. A support address on a domain the brand does not own, or one that bounces, converts a compliance document into a broken promise.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Include a monitored support email on the brand's own domain, or a phone number that is answered, in the SMS terms.",
      "example": "For help with our messaging programme, email support@acmecoffee.com or call 415-555-0134.",
      "catalogIds": [
        "POL-174"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-191",
      "slug": "pol-191",
      "title": "The terms must say the opt-in applies to this programme only",
      "statement": "The SMS terms must state that the opt-in applies only to this brand's programme and is not transferable or assignable.",
      "rationale": "CTIA states that an opt-in should not be transferable, and putting it in the terms is what turns the principle into something the subscriber has been told. Its absence is what a reviewer reads when they are deciding whether a brand understands that consent names a sender — the whole affiliate-marketing prohibition rests on the same idea.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the sentence to the terms, naming the brand rather than saying \"us\". Done when the document states that the consent covers this programme and travels nowhere.",
      "example": "Your consent covers the Acme Coffee Rewards programme only. We do not transfer or assign it to any other business, including any company that might acquire us.",
      "notes": "The positive form of POL-072, which fails a document that describes consent as assignable. This one fails a document that says nothing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-192",
      "slug": "pol-192",
      "title": "A sweepstakes campaign needs its sweepstakes terms in the T&C",
      "statement": "A Sweepstakes campaign's terms must contain the sweepstakes terms, or a link to them.",
      "rationale": "Prize promotions are separately regulated — eligibility, odds, the no-purchase-necessary route, the sponsor — and a subscriber entering by text has been given none of it if the messaging terms do not carry or link the rules. It is also the fastest way for a reviewer to tell a real promotion from a device for harvesting numbers.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Sinch"
      ],
      "applicability": {
        "useCases": [
          "SWEEPSTAKE"
        ]
      },
      "applicabilityText": "Applies when the use case is SWEEPSTAKE.",
      "universal": false,
      "remediation": "Publish the official rules and link them from the SMS terms, or reproduce the material terms there. Done when someone entering by text can reach the rules from the terms in one click.",
      "example": "Full sweepstakes rules, including eligibility and the free entry route, are at acmecoffee.com/sweepstakes-rules.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-193",
      "slug": "pol-193",
      "title": "The programme described in the terms must match the registered use case",
      "statement": "The programme description in the SMS terms must relate to the use case the campaign is registered under.",
      "rationale": "The terms are the public description of the programme and the use case is the private one, so a disagreement means one of them is wrong about what will be sent — and the reviewer resolves it by refusing the campaign. It happens most often when a terms page written for the whole business is attached to a narrower campaign.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms body vs campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "AWS",
        "Twilio",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the terms describe the categories the campaign is registered for, or register the use case the terms describe. Done when the two describe one programme.",
      "example": "Use case MARKETING · terms describe promotional offers and rewards updates — the same programme.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-194",
      "slug": "pol-194",
      "title": "The terms must not say consumer data is shared",
      "statement": "The SMS terms must not indicate that consumer data or opt-in information is shared with third parties.",
      "rationale": "Twilio treats a sharing statement in either the policy or the terms as making the whole business noncompliant, so a careful privacy policy does not protect a terms page that says the opposite. The terms are usually drafted by whoever handles commercial contracts, who has no reason to know the messaging clause exists.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms and ToS body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30932",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove any sharing language from the terms and mirror the privacy policy's non-sharing clause there instead. Done when both documents say the same thing about who gets the number.",
      "example": "We do not share your mobile number or your consent with anyone except the service providers who deliver our messages.",
      "notes": "The same defect in the privacy policy is POL-062 and POL-063; POL-070 is the cross-document contradiction. This rule fails the terms on their own terms, whether or not the policy agrees.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-195",
      "slug": "pol-195",
      "title": "No affiliate-marketing or lead-generation language in the terms",
      "statement": "The SMS terms must contain no affiliate-marketing or lead-generation language anywhere.",
      "rationale": "AWS and Twilio both mark this non-remediable: a rejection under it ends the campaign rather than starting a fix, because the language is read as a description of the business model rather than as a clause. Terms copied from a template that came from an affiliate network carry it without anyone reading it, which is a very expensive way to inherit somebody else's wording.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms and ToS body",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "AWS",
        "Twilio",
        "TCR",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "708",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Delete every reference to affiliates, partners, referral commissions and lead sharing from the terms before submitting, and check the source of any template you used. Done when nothing in the document suggests the subscriber's details reach another business.",
      "example": "Replace \"our partners may contact you with related offers\" with \"only Acme Coffee will text you, and only about the Acme Coffee programme you joined.\"",
      "pitfalls": [
        "The codes are non-remediable, so this must be fixed before submission rather than after a rejection. A campaign rejected under 30951 cannot be edited back into life."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-196",
      "slug": "pol-196",
      "title": "Opt-out information must appear in all three places",
      "statement": "Opt-out information must appear in the call to action, in the terms and conditions, and in the opt-in confirmation message.",
      "rationale": "CTIA asks for all three because they reach the subscriber at three different moments — before consenting, when checking later, and in the first message they receive — and the exit has to be visible at each. Satisfying one of them is the normal state of a programme that has never had this checked, and it is not a partial pass.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "T&C body + call to action + opt-in confirmation",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the STOP instruction in the opt-in disclosure, in the terms, and in the confirmation message. Done when all three carry it in the same words.",
      "example": "All three say: \"Reply STOP to opt out.\"",
      "notes": "A three-surface conjunction. The confirmation-message half is also checked from the message side; this rule is the one that reports the set as incomplete rather than reporting one surface.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-197",
      "slug": "pol-197",
      "title": "The programme name must appear on the call to action and in the terms",
      "statement": "The programme name or product description must appear both on the call to action and in the terms and conditions.",
      "rationale": "The name is what lets a subscriber recognise a message months later as one they agreed to, and it only works if the same name is used where they consented and where the programme is documented. Businesses use a marketing name on the sign-up and the legal entity in the terms, which reads as two programmes.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "T&C body + call-to-action text",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use one programme name in the opt-in copy and in the terms, and register it as the brand DBA or display name. Done when both surfaces name the same programme.",
      "example": "Both the checkout box and the terms say \"Acme Coffee Rewards\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-198",
      "slug": "pol-198",
      "title": "STOP and HELP instructions must be legible — bold is not required",
      "statement": "The STOP and HELP instructions must be legible in the rendered terms; bold typeface is no longer required.",
      "rationale": "The bold requirement came from the legacy MMA guidelines and was withdrawn in CTIA SCMH v1.8, but it survives in circulated checklists — so brands are still being told to reformat documents and tools still flag compliant terms. What survives is the plain requirement underneath: a reader has to be able to find the instructions.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms typography as rendered",
      "severity": "LOW",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Leave the formatting alone if the instructions are readable. Where they are set in small print or the same weight as a wall of text, give them their own line rather than bolding them. Done when a reader can find the STOP instruction at a glance.",
      "example": "A short paragraph of its own: \"To stop receiving messages, reply STOP to any message from us.\"",
      "notes": "Recorded so a legacy checklist does not reintroduce the withdrawn requirement. SCMH v1.8 Appendix B footnote 20 states that opt-out information no longer needs to appear in bold typeface — never report a lack of bold as a defect.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-200",
      "slug": "pol-200",
      "title": "The terms need an acceptance clause",
      "statement": "The terms of service must include an acceptance clause saying that using the service constitutes agreement.",
      "rationale": "Without it the document is a statement of intent rather than an agreement, and nothing in it binds anybody — which matters the first time a business needs to rely on a term. It is also the clause whose absence most obviously marks a page as a stub somebody generated to fill a field.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open the document with the acceptance sentence. Done when the terms say what the reader is agreeing to and by doing what.",
      "example": "By using acmecoffee.com or placing an order with us, you agree to these terms.",
      "notes": "The acceptance clause must not be used as the SMS consent mechanism — agreeing to terms is not agreeing to receive text messages, and treating it as such is a consent-surface defect rather than a drafting one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-201",
      "slug": "pol-201",
      "title": "The terms must name the contracting entity and its address",
      "statement": "The terms of service must identify the legal entity the consumer is contracting with, and its address.",
      "rationale": "The contracting party is who the subscriber has an agreement with, and a reviewer matches that name against the brand record — so terms that say \"we\" throughout leave the registration with no way to bind the document to the business. Trading names make this fail honestly: the site says Acme Coffee, the registration says Acme Coffee Co, LLC, and the terms say neither.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS entity block vs brand.company_name",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR",
        "Twilio",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "807",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the registered legal entity and its address in the opening clause of the terms, alongside the trading name if you use one. Done when the entity in the terms is the entity on the brand record.",
      "example": "These terms are between you and Acme Coffee Co, LLC, 1240 Mission St, Suite 400, San Francisco, CA 94103.",
      "notes": "POL-225 makes the stricter comparison — the same entity name across the policy, the terms and the brand record. This rule is the one-document form, so terms that name no entity at all get a specific finding rather than a mismatch.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-202",
      "slug": "pol-202",
      "title": "The terms should state a minimum age",
      "statement": "The terms of service must include an eligibility clause stating who may use the service and any minimum age.",
      "rationale": "The age line decides which privacy regime the business is in, so a document silent on it leaves the children's section of the policy floating free. It also has to agree with the SMS terms, and where the two differ the pair contradicts itself in a way a reviewer can spot instantly.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the minimum age in the eligibility clause and make it the same number the privacy policy and the SMS terms use. Done when all three agree.",
      "example": "You must be at least 18 to use this site and to place an order.",
      "notes": "The conflict between this age and the privacy policy's children's section is POL-231.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-203",
      "slug": "pol-203",
      "title": "The terms should cover accounts and credentials",
      "statement": "The terms of service must include a clause covering accounts, registration and credential security.",
      "rationale": "Where the service has logins, the account clause is what puts the duty to keep a password safe on the person who holds it — without it the business carries the whole risk of a compromised account. For a messaging programme it also matters that account settings are often where a subscriber manages their preferences.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "excludeUseCases": [
          "M2M"
        ]
      },
      "applicabilityText": "Applies when the use case is NOT M2M.",
      "universal": false,
      "remediation": "Add the accounts clause where the service has user accounts. Done when the document says who is responsible for a login and what happens when it is misused.",
      "example": "You are responsible for keeping your Acme Coffee account password secure and for anything done using your account. Tell us at once if you think someone else has used it.",
      "notes": "Conditional on the service having user accounts, which no applicability dimension expresses; the excludeUseCases tag only keeps it away from machine-to-machine programmes, and the criteria carry the real condition.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-204",
      "slug": "pol-204",
      "title": "The services in the terms must match the campaign description",
      "statement": "The services or products described in the terms must be consistent with what the campaign description says the business does.",
      "rationale": "The two documents describe the same business to two different audiences, and a reviewer reads them together — terms describing a software subscription behind a campaign describing a coffee shop is the shape of a registration assembled from someone else's material. It also happens innocently when a business pivots and the terms are the last thing updated.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS services section vs campaign.description",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30886",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Bring the services section into line with what the business actually sells, and check it against the campaign description before submitting. Done when both describe the same business.",
      "example": "Terms: \"Acme Coffee sells roasted coffee, subscriptions and brewing equipment.\" Campaign description names the same business.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-205",
      "slug": "pol-205",
      "title": "A retail brand's terms should cover orders and payment",
      "statement": "The terms of service must include a commerce section covering orders, pricing, payment, taxes, subscriptions and auto-renewal.",
      "rationale": "For a retailer these clauses are the actual agreement — what a price means, when an order is accepted, what happens when a subscription renews — and their absence is what leaves a business arguing with a customer over a charge with nothing to point at. The auto-renewal element carries independent legal weight in several states.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "verticals": [
          "RETAIL"
        ]
      },
      "applicabilityText": "Applies when the vertical is RETAIL.",
      "universal": false,
      "remediation": "Add the commerce section covering ordering, pricing, payment and any recurring charge. Done when a customer can find out what happens when their subscription renews.",
      "example": "Coffee subscriptions renew every four weeks at the price shown when you subscribed. We email you three days before each renewal, and you can cancel any time from your account.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-206",
      "slug": "pol-206",
      "title": "A retail brand's terms should cover shipping and returns",
      "statement": "The terms of service must include a section covering shipping, returns, refunds and cancellations, or links to those pages.",
      "rationale": "These are the terms customers actually look for, and for a messaging programme they are frequently the subject of the messages — an order update points at a delivery promise that has to exist somewhere. A link to a dedicated page is fine; silence is what leaves the business with no stated policy at the moment it is being asked to honour one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "verticals": [
          "RETAIL"
        ]
      },
      "applicabilityText": "Applies when the vertical is RETAIL.",
      "universal": false,
      "remediation": "Cover the four, or link the pages that do. Done when a customer reading the terms can find the returns window.",
      "example": "Shipping, returns and refunds are covered at acmecoffee.com/shipping and acmecoffee.com/returns, which form part of these terms.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-207",
      "slug": "pol-207",
      "title": "The terms should include an acceptable-use clause",
      "statement": "The terms of service must include an acceptable-use or prohibited-conduct clause.",
      "rationale": "The clause is what lets a business remove a user who is abusing the service, and without it every enforcement decision is unsupported. For a brand running a two-way messaging programme it also covers the inbound side, where abusive replies otherwise have no stated consequence.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the acceptable-use clause naming the prohibited conduct and the consequence. Done when the terms say what would get someone removed.",
      "example": "Do not use our site or our text programme to send unlawful, abusive or fraudulent content. We may suspend access for anyone who does.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-208",
      "slug": "pol-208",
      "title": "The terms should cover user-generated content",
      "statement": "The terms of service must include a user-content clause covering licence, takedown and a DMCA agent.",
      "rationale": "Where customers post reviews, photos or replies the business needs permission to display them, and the licence clause is that permission — without it every republished review is unlicensed. The DMCA agent is what preserves the safe harbour when somebody complains about a post.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the user-content clause where the service accepts any user submissions, including reviews. Done when the terms say what you may do with what customers post and how to complain about a post.",
      "example": "When you post a review or photo you give Acme Coffee permission to display it. To report content, email legal@acmecoffee.com.",
      "notes": "Conditional on the service accepting user content, which no applicability dimension expresses; the criteria pass immediately where it does not.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-209",
      "slug": "pol-209",
      "title": "The terms should state intellectual-property ownership",
      "statement": "The terms of service must include a clause stating who owns the site content and the marks.",
      "rationale": "The clause is what the business relies on when its product photography or its name is copied, and its absence is one of the markers of a generated stub. It is also the section a reviewer scanning for template output notices first, because it is the one that names the brand most often.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State ownership in the brand's own name and say what use is permitted. Done when the clause names your business rather than a template's.",
      "example": "The Acme Coffee name, logo and all content on this site belong to Acme Coffee Co, LLC. You may not use them without our permission.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-210",
      "slug": "pol-210",
      "title": "The terms should disclaim third-party links and services",
      "statement": "The terms of service must include a third-party links and services disclaimer.",
      "rationale": "A messaging programme sends links, and some of them lead off the brand's own site — to a delivery carrier, a booking platform, a payment page — so the disclaimer covers destinations the business does not control. Without it, the terms implicitly stand behind every page a message can reach.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the disclaimer and name the kinds of third-party service you link to. Done when the terms make clear which destinations you stand behind.",
      "example": "Our site and our messages link to services we do not control, such as delivery tracking. Their own terms and privacy policies apply there.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-212",
      "slug": "pol-212",
      "title": "Email and other-channel marketing terms must be kept separate from SMS consent",
      "statement": "Marketing terms for email and other channels must be kept separate from the SMS consent terms.",
      "rationale": "One control covering several channels is the bundled-consent defect Twilio codes at 30913, and the document is where the bundling is usually written down first — \"by subscribing you agree to receive email and text messages\" makes it official. Separating them in the terms is what lets a subscriber take one and not the other, which is the whole point.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS marketing-communications section",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30913",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Give the SMS programme its own section, with its own consent statement, rather than describing it inside a general marketing-communications clause. Done when a reader can see that agreeing to one channel does not sign them up for the other.",
      "example": "Email newsletter and text messaging are separate programmes with separate sign-ups. Joining one does not join you to the other.",
      "notes": "Graded LOW because the document form of the defect is a drafting question; the BLOCKING form — a single control collecting consent for both — lives in the consent-surface layer, where the harm actually happens.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-213",
      "slug": "pol-213",
      "title": "The terms should allocate risk",
      "statement": "The terms of service must include a disclaimer of warranties, a limitation of liability and an indemnity.",
      "rationale": "These three clauses are the entire risk position of the agreement, and a business without them is exposed to consequences it never priced. For a messaging programme the limitation matters specifically: undelivered or delayed messages are outside anybody's control, and the carrier disclaimer the SMS terms carry needs a limitation clause behind it to mean anything.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add all three, and make the limitation consistent with the carrier disclaimer in the SMS terms. Done when the document says what you do not promise and what you are not liable for.",
      "example": "The site is provided as is. To the extent the law allows, Acme Coffee is not liable for indirect or consequential loss, and you agree to indemnify us against claims arising from your misuse of the site.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-214",
      "slug": "pol-214",
      "title": "Arbitration and class-waiver language needs a lawyer",
      "statement": "Arbitration, class-action-waiver and governing-law language must be reviewed by counsel rather than generated.",
      "rationale": "These clauses are jurisdiction-sensitive, increasingly challenged, and drafted wrongly they are struck out — taking the protection with them and sometimes more of the document besides. A generated arbitration clause is the single highest-risk thing a document tool can emit, because it reads as authoritative and its defects are invisible until it is relied on.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS arbitration and dispute-resolution clauses",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Have counsel draft or review the dispute-resolution clauses for the states you operate in, before publishing. Done when a lawyer has signed off on the arbitration and class-waiver wording specifically.",
      "notes": "Absorbs POL-245, which states the same escalation for generated clauses. Not something the registry should settle or the product should generate unreviewed: the user has to route these clauses to a lawyer, and a draft that contains them should say so at export.",
      "catalogIds": [
        "POL-245"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Has a lawyer signed off on the arbitration and class-waiver wording specifically, for the states you operate in?",
        "howToCheck": [
          "Identify the dispute-resolution clauses in the terms: arbitration, class-action waiver, governing law.",
          "Have counsel draft or review them before publishing — not the document in general, these clauses in particular."
        ],
        "failureLooksLike": "A generated arbitration clause reads as authoritative and its defects are invisible until it is relied on. Drafted wrongly it is struck out, taking the protection with it and sometimes more of the document besides."
      }
    },
    {
      "id": "POL-215",
      "slug": "pol-215",
      "title": "The terms should state governing law and venue",
      "statement": "The terms of service must include a governing-law and venue clause.",
      "rationale": "Without it a dispute starts with an argument about where the dispute happens, which is expensive before anything about the substance is discussed. It is also one of the clauses whose absence marks a document as generated rather than drafted, because generators frequently leave the jurisdiction as a placeholder.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the state whose law governs and the courts that hear disputes. Done when neither is a placeholder.",
      "example": "These terms are governed by the laws of the State of California, and disputes are heard in San Francisco County.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-216",
      "slug": "pol-216",
      "title": "The terms should cover termination",
      "statement": "The terms of service must include a termination and suspension clause.",
      "rationale": "Both sides need a stated way out — the customer to close an account, the business to end a relationship — and without one every ending is improvised. For a messaging programme it pairs with the right to end the programme that the SMS terms carry.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the clause covering both directions and what survives. Done when the terms say how the relationship ends.",
      "example": "You can close your Acme Coffee account at any time. We may suspend or close an account that breaches these terms. Sections on liability and governing law survive.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-217",
      "slug": "pol-217",
      "title": "The terms should carry the standard boilerplate",
      "statement": "The terms of service must include the standard boilerplate: severability, assignment, entire agreement, waiver, force majeure and notices.",
      "rationale": "Each of these does one job that only shows up when something goes wrong — a struck-out clause taking the rest with it, a right lost because it was not enforced once — and their absence is the clearest signal that a document was assembled rather than drafted. The assignment clause needs care for a messaging programme: consent must be excluded from it, which is POL-072.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms of service body",
      "severity": "LOW",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the boilerplate block, and exclude messaging consent from the assignment clause explicitly. Done when all six are present and the assignment clause names the carve-out.",
      "example": "If any part of these terms is unenforceable the rest continues to apply. We may assign these terms; your text messaging consent is not assigned with them.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-219",
      "slug": "pol-219",
      "title": "The terms must carry a contact block",
      "statement": "The terms of service must carry a contact block naming the entity, its address, an email address and a phone number.",
      "rationale": "The contact block doubles as the customer-care route the messaging framework separately requires, so a terms page without one leaves both a contractual and a messaging obligation unmet. It is also the block a reviewer reads to corroborate the brand, which is why a bare contact-form link does not do the job.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS contact block",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "Sinch",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "852",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Close the terms with a contact block carrying the entity, the postal address, a monitored email and a phone number, matching the brand record. Done when a reader can reach a person without using a form.",
      "example": "Questions? Acme Coffee Co, LLC, 1240 Mission St, Suite 400, San Francisco, CA 94103 · support@acmecoffee.com · 415-555-0134",
      "notes": "Overlaps deliberately with POL-190, which asks the SMS terms for a support contact: a brand that publishes dedicated SMS terms satisfies both from one block, and a brand that does not still owes the contact here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-225",
      "slug": "pol-225",
      "title": "Legal entity name must be identical across policy, terms and brand record",
      "statement": "The legal entity named in the privacy policy, in the terms, and on the TCR brand record must be the same name.",
      "rationale": "Reviewers use the entity name to confirm the policy actually belongs to the registering brand rather than being borrowed from a parent, a franchisor, or another business entirely. Three different names across three documents reads as exactly that, and it is a fast rejection because it needs no judgement.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms + brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the exact registered legal name in both documents. If a parent company owns the policy, add a line naming this brand as a covered entity rather than leaving the mismatch unexplained.",
      "example": "All three read \"Acme Coffee Co, LLC\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-226",
      "slug": "pol-226",
      "title": "The brand name must be identical across samples, SMS terms and the opt-in disclosure",
      "statement": "The brand or DBA used in the message samples must match the brand name in the SMS terms and the name shown in the opt-in disclosure.",
      "rationale": "A consumer encounters the brand name three times — when they opt in, in the messages, and in the terms they consult later — and reviewers check that all three describe one sender. Marketing teams routinely use a shortened trading name in messages and the legal name in the terms, which reads as two different businesses sharing a programme.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "campaign.sample[] + terms body + consent disclosure",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "TCR",
        "CTIA",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Choose one customer-facing name and use it identically in the samples, in the SMS terms, and in the opt-in disclosure. Register that name as the brand display name or DBA so the registration matches too.",
      "example": "All three use \"Acme Coffee\" (registered as the DBA of Acme Coffee Co, LLC).",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-228",
      "slug": "pol-228",
      "title": "The terms, the use case and the samples must promise the same messages",
      "statement": "The message types described in the SMS terms, the registered use case and the sample messages must all agree.",
      "rationale": "Three artifacts describe the same programme and a reviewer compares all three, so the weakest one sets the verdict — samples showing promotions under a transactional use case with terms describing order updates is three different answers to one question. Each is written at a different time by a different person, which is exactly why they drift.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms message types + campaign.usecase + samples",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30893",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick the true description of the programme and make all three match it, widening the use case if the samples need it. Done when the categories in the terms, the use case and the samples are the same set.",
      "example": "Use case MIXED · terms describe offers and order updates · samples show one of each.",
      "notes": "POL-193 compares the terms against the use case alone. This rule adds the samples, which is the surface most likely to reveal a category nobody declared.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-229",
      "slug": "pol-229",
      "title": "The support contact in the terms must match the HELP reply",
      "statement": "The support email or phone number in the SMS terms must be one the HELP auto-reply also gives.",
      "rationale": "A subscriber who texts HELP and a subscriber who reads the terms should reach the same place, and when they do not, one of the two routes is usually a mailbox nobody watches. The mismatch appears when support changes address and only the auto-reply is updated, because that is the one somebody tests.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms contact block vs campaign.help_message",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the same support address and number in the HELP reply and in the terms. Done when at least one contact route appears in both, and both are monitored.",
      "example": "HELP reply and SMS terms both give support@acmecoffee.com.",
      "notes": "Requires an overlap rather than an identical set: a HELP reply that gives an email and a phone number while the terms give only the email is consistent, and failing it would push brands to pad both documents.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-230",
      "slug": "pol-230",
      "title": "Keywords in the SMS terms must match the keywords declared on the campaign",
      "statement": "The opt-out keywords listed in the published SMS terms must be the same set declared on the campaign registration.",
      "rationale": "A consumer relies on the terms, a reviewer relies on the registration, and the platform honours whatever it was configured with. When those three disagree, someone texts a keyword that is published but not honoured — which is a Severity-0 style failure dressed up as a documentation inconsistency.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "terms body + campaign.optout_keywords",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the published keyword list and the declared keyword list identical, and confirm your platform honours every keyword in it.",
      "example": "Both list: STOP, QUIT, END, REVOKE, OPTOUT, CANCEL, UNSUBSCRIBE.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-231",
      "slug": "pol-231",
      "title": "The ages in the terms and the privacy policy must not conflict",
      "statement": "The minimum age in the terms' eligibility clause must not conflict with the age threshold in the privacy policy's children's section.",
      "rationale": "Terms admitting thirteen-year-olds next to a policy promising to collect nothing from anyone under eighteen is a document pair that cannot both be true, and a reviewer reading them together sees a business that has not read its own paperwork. The two are almost always written by different people from different templates, which is why the numbers diverge without anybody deciding anything.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "ToS eligibility age vs privacy policy minors section",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Pick one age for the business and use it in the terms, the privacy policy and the SMS terms. Done when the same number appears in all three.",
      "example": "Terms: \"You must be at least 18.\" Privacy policy: \"We do not knowingly collect information from anyone under 18.\"",
      "notes": "The check compares only explicit numbers it can find in both documents. Where either is silent it reports PASS rather than inventing a conflict — the absence of the age itself is POL-202 and POL-113.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-232",
      "slug": "pol-232",
      "title": "Every opt-in surface on the site must appear in the terms",
      "statement": "Every opt-in surface found by crawling the site must be represented in the list of opt-in methods in the SMS terms.",
      "rationale": "This is the same question as POL-181 asked from the other side, and it catches the case the declared flow cannot: a sign-up form somebody built for a campaign months ago, still live, feeding the same programme. A subscriber who joined through it reads terms that do not describe how they got there.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "crawled opt-in surfaces vs SMS terms opt-in list",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Inventory every form on the site that collects a mobile number for messaging, then either list it in the terms or take it down. Done when the terms describe every live route into the programme.",
      "notes": "Decided from the crawl rather than the declared flow, which is why it is separate from POL-181 — the two disagree independently, and this is the one that finds the surface nobody remembered.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-233",
      "slug": "pol-233",
      "title": "The document dates must not predate the last change to the programme",
      "statement": "The last-updated dates on the privacy policy and the terms must be no older than the last material change to the messaging programme.",
      "rationale": "A programme that gained a message category last month, documented by terms dated two years ago, is telling subscribers about a programme that no longer exists — and CTIA asks for up-to-date information rather than merely dated information. The dates are the cheapest way for a reviewer to spot it, which makes a stale date an invitation to read the rest more carefully.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms dates vs the last programme change",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Update both documents whenever the programme changes materially — a new message category, a new opt-in surface, a changed frequency — and set the date by hand when you do. Done when both dates are at least as recent as the last change.",
      "example": "Programme gained cart reminders in June 2026; both documents now read \"Last updated: 1 July 2026\".",
      "notes": "Authored as a judgement rather than as a predicate on purpose. Comparing a published date against a change that happened \"N days ago\" needs a clock, and a rule whose verdict changes with the calendar cannot be pinned by a fixture — the corpus would drift silently. The judge is given the campaign fields, which carry how long ago the programme last changed where the caller knows it. Whether a date is present at all is POL-141.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-234",
      "slug": "pol-234",
      "title": "An opt-in incentive must be described in both documents",
      "statement": "Where joining the programme earns a discount or reward, both the SMS terms and the privacy policy's financial-incentive notice must describe it.",
      "rationale": "The offer is the reason most people join, so it is part of the deal rather than a marketing detail — and California treats it as a financial incentive with its own disclosure. A discount described on the checkout box and nowhere else leaves the subscriber unable to check what they were promised.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "SMS terms + privacy policy incentive language vs the opt-in offer",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "sms_terms",
      "authorities": [
        "CCPA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe the incentive in the terms — what it is, what it requires, when it expires — and cover it in the policy's financial-incentive notice. Done when the offer on the opt-in is documented in both.",
      "example": "Join Acme Coffee Rewards by text and get 10% off your next order. The code arrives in your welcome message and is valid for 30 days.",
      "notes": "Conditional on the opt-in offering an incentive, which no applicability dimension expresses; the criteria pass immediately where none is offered. The policy-side twin is POL-111.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-240",
      "slug": "pol-240",
      "title": "Template policy and website content is a known reject cause",
      "statement": "Policy and terms text recognisable as generator boilerplate must be flagged, because reviewers reject it as evidence the business is not real.",
      "rationale": "Free generators produce documents that are word-for-word identical across thousands of sites, and reviewers have seen them all — so the document that took five minutes to produce is the one that costs a rejection. It is the most common surprise in this whole layer, because the business believes having a policy was the requirement.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "privacy policy + terms body",
      "severity": "MEDIUM",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "Plivo",
        "Telnyx",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "7103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the generic sections with text about this business — the products it sells, the surfaces it collects numbers on, the vendors it actually uses. Done when nothing in the document would be true of an unrelated company.",
      "example": "Replace \"We may collect information from you when you visit our website\" with \"We collect your mobile number when you tick the text box at checkout on acmecoffee.com.\"",
      "notes": "The positive form is POL-241, which asks whether brand-specific facts are present. This one detects the generic text; that one detects its absence, and a document can fail both.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-241",
      "slug": "pol-241",
      "title": "A generated document must carry brand-specific facts",
      "statement": "A generated policy or terms document must contain facts specific to this brand — real product names, the actual opt-in surfaces, the actual vendors.",
      "rationale": "A document that could belong to anybody tells a reviewer that nobody described this business to whoever wrote it, and the reviewer's next question is whether the business exists. It is also the difference between a policy that is true and one that is merely plausible: the specifics are what make its promises checkable against the site.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "generated policy and terms body",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the business, the site, the opt-in surfaces, the message categories and the delivery vendor in the document itself. Done when a reader could identify the business from the policy alone.",
      "example": "Acme Coffee Rewards collects your number at checkout on acmecoffee.com and sends it to our messaging vendor, who delivers the texts. Nobody else receives it.",
      "notes": "Absorbs OPS-377, which states the same warning from the operational side. POL-240 is the detection of generic text in an existing document; this is the requirement on one we produce.",
      "catalogIds": [
        "OPS-377"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-242",
      "slug": "pol-242",
      "title": "Never assert a fact about the business nobody has confirmed",
      "statement": "A generated document must assert no fact about the business — retention periods, encryption, sub-processors, certifications, jurisdictions — that has not been sourced or confirmed by the user.",
      "rationale": "Every invented fact in a privacy policy is a public representation the business will be held to, and the ones a generator reaches for are exactly the ones nobody checks: a thirty-day retention period, AES-256 at rest, a SOC 2 report that does not exist. The FTC treats a false privacy claim as a deceptive practice, so this is the one obligation in the layer where the consequence is an enforcement action rather than a rejection.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "generated policy factual assertions",
      "severity": "BLOCKING",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "FTC",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm each factual claim with the person who would know — retention with whoever runs the database, encryption with whoever runs the infrastructure, certifications with whoever holds the audit report — and delete the ones nobody can confirm. Done when every specific in the document traces to somebody who said it.",
      "notes": "Nothing in a registration reveals whether a claim was confirmed, which is why this cannot be settled here. The user has to read the generated document as a set of assertions about their own business and strike out everything they cannot personally source. Invented SOC 2, ISO 27001 and HIPAA claims are the specific ones the research names.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Can you name a person who confirmed each specific claim in this document — retention periods, encryption, sub-processors, certifications?",
        "howToCheck": [
          "Read the document as a list of assertions about your own business.",
          "For each specific, ask the person who would know: retention with whoever runs the database, encryption with whoever runs the infrastructure, certifications with whoever holds the audit report.",
          "Delete every claim nobody can source. Invented SOC 2, ISO 27001 and HIPAA claims are the named ones."
        ],
        "failureLooksLike": "A thirty-day retention period and AES-256 at rest arrive in the draft because they are plausible. The FTC treats a false privacy claim as a deceptive practice, so the consequence is an enforcement action rather than a rejection."
      }
    },
    {
      "id": "POL-243",
      "slug": "pol-243",
      "title": "Mark every unverified variable and block export until it is resolved",
      "statement": "Every unverified variable in a generated draft must be visibly marked, and export must be blocked until each is resolved.",
      "rationale": "The alternative to marking is guessing, and a guessed retention period ships as a fact — so the marker is what keeps POL-242 enforceable rather than aspirational. The published-document version of this failure is trivially detectable and embarrassingly common: a live policy with {{COMPANY_NAME}} still in it, which is what happens when a draft is exported past its own placeholders.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "draft policy placeholder tokens",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_ARTIFACT",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Leave every unconfirmed value as a visible token rather than a plausible default, and resolve each before publishing. Done when a search of the draft for \"{\" and \"[\" returns nothing.",
      "notes": "The publication-side twin is POL-156, which fails a live page carrying tokens; this is the gate before that page exists. We hold no draft, so the user owns it: do not publish a generated document until every marked variable has been replaced with a value somebody confirmed.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does a search of this draft for \"{\" and \"[\" return nothing?",
        "howToCheck": [
          "Search the draft for both characters before publishing.",
          "Resolve each marked variable with a value somebody confirmed, rather than a plausible default.",
          "Do not publish while any marker remains."
        ],
        "failureLooksLike": "A live policy with {{COMPANY_NAME}} still in it — trivially detectable, embarrassingly common, and what happens when a draft is exported past its own placeholders."
      }
    },
    {
      "id": "POL-244",
      "slug": "pol-244",
      "title": "Do not silently overwrite a policy the brand already publishes",
      "statement": "Where the brand already publishes a policy, a generated replacement must be diffed against it and the removed clauses surfaced for review.",
      "rationale": "An existing policy usually contains commitments somebody made deliberately — a sector clause, a jurisdiction addendum, a promise made to a customer — and replacing it wholesale deletes them without anyone seeing what went. The business ends up less compliant than before, having run a compliance tool.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "existing published policy vs the generated draft",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Diff the draft against the live page and read the removals before publishing, keeping anything that was there for a reason. Done when every clause the replacement drops has been looked at by a person.",
      "notes": "We can fetch the live policy but we cannot know which of its clauses were deliberate. The user has to compare the two documents themselves and decide what to carry across — sector language and anything added after a complaint or a legal review are the clauses worth looking for first.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Have you read what the replacement removes from the policy this brand already publishes?",
        "howToCheck": [
          "Diff the draft against the live page.",
          "Read the removals, looking first for sector language and anything added after a complaint or a legal review.",
          "Keep whatever was there for a reason."
        ],
        "failureLooksLike": "A wholesale replacement deletes a jurisdiction addendum and a promise made to a customer. The business ends up less compliant than before, having run a compliance tool."
      }
    },
    {
      "id": "POL-246",
      "slug": "pol-246",
      "title": "Export must carry a visible not-legal-advice disclaimer",
      "statement": "A generated policy or terms document must be exported with a visible disclaimer that it is not legal advice and should be reviewed by counsel.",
      "rationale": "A document that arrives formatted and complete reads as advice whether or not it was meant as such, and the businesses most likely to rely on it are the ones least able to absorb the consequences of it being wrong. Every vendor in this space carries the disclaimer, which is a fair indication of what happens without it.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "document export",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Show the disclaimer where the document is exported, not buried in a terms page nobody opens. Done when the person pressing export has read that it is not legal advice.",
      "notes": "A product obligation with no registration surface: nothing in a submission records what the export screen said. The user has to treat any generated document as a starting draft and have it reviewed, particularly the dispute-resolution clauses POL-214 flags.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Did the person exporting this document see, and read, that it is not legal advice?",
        "howToCheck": [
          "Show the disclaimer where the export happens, not buried in a terms page nobody opens.",
          "Treat the output as a starting draft and route it for review — the dispute-resolution clauses first."
        ],
        "failureLooksLike": "A document that arrives formatted and complete reads as advice whether or not it was meant as such, and the businesses most likely to rely on it are the least able to absorb it being wrong."
      }
    },
    {
      "id": "POL-247",
      "slug": "pol-247",
      "title": "Do not claim a generated document makes the brand compliant",
      "statement": "Neither the product nor its output may claim that a generated policy makes the brand compliant or approved.",
      "rationale": "Carriers and the DCAs make that determination, and nobody else can — so a claim of compliance is both untrue and the reason a business stops checking. It is a specific failure of tools like this one: the more confident the output looks, the less likely anyone is to read the rejection reasons when they arrive.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "product export copy",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Describe what the document does — it covers the requirements the registry knows about — rather than what it achieves. Done when nothing in the output or the interface promises approval.",
      "notes": "Nothing in a registration records the claims made around it. The obligation is on whoever writes the product copy, and on the user not to treat a clean run here as an approval: the registry is a strict superset of what was researched, not a guarantee about any particular reviewer.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does anything you say about this output promise compliance or approval, rather than describing what it covers?",
        "howToCheck": [
          "Read the interface copy and the document's own front matter for any claim of being compliant, approved, or carrier-ready.",
          "Describe what it does — it covers the requirements the registry knows about — rather than what it achieves.",
          "As a user: do not treat a clean run as an approval."
        ],
        "failureLooksLike": "The more confident the output looks, the less likely anyone is to read the rejection reasons when they arrive. Carriers and the DCAs make that determination and nobody else can."
      }
    },
    {
      "id": "POL-248",
      "slug": "pol-248",
      "title": "Record who approved the document and when",
      "statement": "The business should record who approved each published policy or terms document, and when.",
      "rationale": "An appeal or a complaint turns on who decided what and when they decided it, and a document with no approver is one nobody owns — which is exactly the state that lets a stale policy sit live for two years. The record costs a line and it is the difference between an audit trail and a reconstruction.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "document approval record",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep a record of the approver, the date and the version alongside the published document. Done when you can say who signed off the text that is currently live.",
      "notes": "Internal to the business and invisible to any registration. The user has to keep the record themselves — the same place the POL-028 capture lives is the natural home for it, since the two answer the same question about the same document.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Can you say who signed off the policy text that is live right now, and when?",
        "howToCheck": [
          "Record the approver, the date and the version alongside the published document.",
          "Keep it where the submission-day capture lives — the two answer the same question about the same document."
        ],
        "failureLooksLike": "A document with no approver is one nobody owns, which is exactly the state that lets a stale policy sit live for two years."
      }
    },
    {
      "id": "POL-249",
      "slug": "pol-249",
      "title": "A regulated vertical needs a warning that sector rules sit on top",
      "statement": "Where the brand is in a regulated vertical, the generated documents must be accompanied by a warning that sector rules apply on top of them.",
      "rationale": "A generated policy that satisfies every carrier requirement can still be badly wrong for a clinic, a lender, a school or an insurer, because HIPAA, GLBA and FERPA impose obligations no 10DLC checklist mentions. The danger is specific to generation: a document that looks finished stops the reader looking for what is missing.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "generated policy + product warning",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the brand is in healthcare, financial services, insurance or education, say plainly that the sector's own rules are not covered here and route the document to someone who knows them. Done when the warning names the applicable regime rather than warning in general.",
      "example": "This policy covers the messaging requirements. It does not address HIPAA, which applies to Riverside Dental as a covered entity — have your privacy officer review it before publishing.",
      "notes": "Conditional on the vertical, which the registration carries as free text rather than as a controlled list, so the condition sits in the criteria rather than in a tag. The verticals named in the catalog are financial services, healthcare, education and insurance.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-250",
      "slug": "pol-250",
      "title": "A generated policy must not describe practices the site does not perform",
      "statement": "A generated policy must not describe practices the brand does not actually perform.",
      "rationale": "Generation makes this failure easy in a way that drafting does not: a template section about cookies goes into a document for a site that has none, or a \"we do not use tracking\" line goes into a document for a site loading three analytics tags. The result is a public statement that is false, which is an FTC deception question rather than a carrier one.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "generated policy body vs the site's observed behaviour",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check every practice claim against the live site before publishing, and delete the sections that describe things the business does not do. Done when each claim in the document can be pointed at on the site.",
      "notes": "The machine-checkable half of POL-108, which asks the same question of a policy the brand already published. This one is the generation-time gate, so it is the one that can stop the false statement being made at all.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "POL-251",
      "slug": "pol-251",
      "title": "Say that carriers, not CTIA, enforce the non-sharing language",
      "statement": "The product should disclose that the SMS non-sharing language is a carrier and TCR requirement rather than a CTIA one.",
      "rationale": "Guidance across the industry attributes the mobile-data non-sharing clause to CTIA, and CTIA has never asked for it — so a business that goes to the source to check finds nothing and concludes the requirement is invented. Being explicit that the registry generates to the stricter carrier standard on purpose is what stops that conversation ending with the clause being removed.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "product export disclosure copy",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State the attribution where the clause is generated: carriers and TCR require it, CTIA does not. Done when a user who checks the CTIA handbook and finds nothing still understands why the clause is there.",
      "notes": "An honesty requirement about our own output, with no registration surface. POL-050 carries the same correction in its rationale; this rule is the obligation to repeat it to the user at the point they are deciding whether to publish the sentence.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does whoever decides whether to publish the non-sharing clause know that carriers and TCR require it, and CTIA does not?",
        "howToCheck": [
          "State the attribution where the clause is generated.",
          "Done when someone who checks the CTIA handbook, finds nothing, and comes back still understands why the clause is there."
        ],
        "failureLooksLike": "Industry guidance attributes the clause to CTIA, the business goes to the source to check, finds nothing, concludes the requirement is invented — and deletes the sentence."
      }
    },
    {
      "id": "POL-252",
      "slug": "pol-252",
      "title": "A sole proprietor still needs a published policy",
      "statement": "Policy pages must be produced for a sole-proprietor brand on the same terms as for any other entity type.",
      "rationale": "One vendor claims sole proprietors are exempt from a compliant privacy policy and another lists sole-proprietor campaigns among its own rejection categories, so acting on the exemption means betting the registration on a single unverified source. The people it would affect are the least equipped to absorb a rejection, which is why the safe reading is to generate the document anyway.",
      "layer": "POLICY_PAGE",
      "layerSlug": "policy-page",
      "object": "brand.entityType vs the policy generation gate",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_POLICY",
      "artifact": "privacy_policy",
      "authorities": [
        "TCR",
        "Bandwidth"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Publish a privacy policy and SMS terms for the sole-proprietor brand and register both URLs, exactly as for a company. Done when the sole-proprietor campaign carries the same two links a standard campaign would.",
      "example": "privacy_policy_url: https://janedoe.com/privacy · terms_and_conditions_url: https://janedoe.com/sms-terms",
      "notes": "The catalog records this as contested: Alive5 claims the exemption as a single unverified source, Bandwidth separately lists sole-proprietor campaigns as a rejection category. The exemption is treated as unproven rather than asserted either way.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-001",
      "slug": "web-001",
      "title": "A brand website URL is required",
      "statement": "Every brand registration must carry a website URL or an equivalent hosted online-presence artifact.",
      "rationale": "The website is the primary artifact a reviewer uses to decide the business is real and does what the campaign claims. Without one there is nothing to corroborate the registration against, and most providers will not vet a brand at all.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "Telnyx",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the business website. If the business genuinely has no site, a public, indexable business profile page can substitute at some providers — but expect additional scrutiny and a slower vet.",
      "example": "https://acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-004",
      "slug": "web-004",
      "title": "The website value must fit the provider field limit",
      "statement": "The website URL must fit within the provider field cap (100 characters at the tightest).",
      "rationale": "Some providers cap the website field at 100 characters and truncate silently rather than erroring, which produces a stored URL that no longer resolves. A deep link with tracking parameters overruns easily.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Telnyx",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use the bare site root without tracking parameters or deep paths in the brand website field.",
      "example": "https://acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-006",
      "slug": "web-006",
      "title": "The website should sit on a custom domain",
      "statement": "The brand website should be on a domain the business owns, not a free platform subdomain.",
      "rationale": "A free platform subdomain costs nothing and takes minutes to create, so it carries almost no evidential weight about a business existing. Reviewers treat it as a weak signal rather than an automatic rejection, but it materially raises the chance of a manual review.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Point a custom domain at the site and register that domain as the brand website. Keep the brand email on the same domain so the two corroborate each other.",
      "example": "https://acmecoffee.com rather than https://acmecoffee.myshopify.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-009",
      "slug": "web-009",
      "title": "The website must resolve and return content",
      "statement": "The submitted website URL must resolve to a live host and return a successful response.",
      "rationale": "A site that does not load cannot be assessed, and DNS or timeout failures are indistinguishable to a reviewer from a business that does not exist. This is checked at submission and again during vetting, so intermittent hosting fails it twice.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm the URL loads from a clean network with no VPN, and check DNS resolves publicly. Fix hosting before submitting rather than resubmitting hopefully.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-012",
      "slug": "web-012",
      "title": "A parked or for-sale domain is not a website",
      "statement": "A registrar placeholder, parked domain, or domain-for-sale page is rejected.",
      "rationale": "A parked domain proves someone bought a name, not that a business operates. It is a strong fraud signal because it is exactly what a registration created to obtain numbers looks like.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the real site at that domain, or submit the domain where the business actually operates.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-013",
      "slug": "web-013",
      "title": "A pre-launch placeholder is not a website",
      "statement": "A \"coming soon\" or \"under construction\" page is rejected as the brand website.",
      "rationale": "A business not yet open to customers has no consenting subscribers to message, so the placeholder page and the campaign contradict each other. Reviewers read it as registering ahead of having a programme.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Launch the site before registering. Register once you have a live site and real subscribers.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-014",
      "slug": "web-014",
      "title": "Staging, admin and internal hosts must not be the brand website",
      "statement": "Non-production hosts — staging, dev, preview, admin, internal — are not acceptable brand websites.",
      "rationale": "A staging host is not the business's public presence, is frequently access-gated, and often disappears between submission and review. Submitting one usually means someone pasted the URL they had open rather than the live site.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Submit the public production URL customers actually visit. If the staging host is the only one currently live, launch the production site before registering — the vetter will re-fetch this URL days later and a staging box is usually gone by then.",
      "example": "https://acmecoffee.com rather than https://staging.acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-015",
      "slug": "web-015",
      "title": "Pages the registration points at must not be broken",
      "statement": "Any page the registration references must load — no broken internal links, 404s, or links pointing at the wrong page.",
      "rationale": "A reviewer following a link from the registration into a 404 concludes the site is unmaintained, and unmaintained sites are the ones that disappear between submission and the vet weeks later. The links that break are almost always the ones nobody clicks: the policy page renamed during a redesign, the old opt-in path left in the message flow.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "internal links on the referenced pages",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "Bird",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Bandwidth/DCA",
          "code": "804",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Click every URL in the registration and every link on the pages they open, and fix or remove the ones that fail. Done when the policy, terms and opt-in links all resolve to the pages they name.",
      "notes": "Needs a multi-page crawl to settle properly. Against the current single-page fetch it can only see links on the one page retrieved, and must say so rather than reporting the rest as sound.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-016",
      "slug": "web-016",
      "title": "The website must be machine-crawlable",
      "statement": "The site must be fetchable by an automated vetter — no robots.txt disallow, no noindex, no JS-only render, no geofence.",
      "rationale": "Vetting uses a headless fetcher, not a browser. A single-page app that renders only after JavaScript returns an empty shell to the reviewer, so a fully compliant opt-in becomes invisible and the campaign is rejected for having no discoverable consent flow.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Server-render the opt-in page, or provide a server-rendered alternative URL carrying the same consent surface. Remove robots.txt disallows on that path and confirm the page returns content from a US IP with JavaScript disabled.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-017",
      "slug": "web-017",
      "title": "The site must not be gated by geo, bot challenge or app wall",
      "statement": "A geo-gate, bot challenge, captive portal, or app-install wall blocking the reviewer is rejected.",
      "rationale": "Vetting fetches from a US datacentre IP, which aggressive bot protection frequently blocks outright. The site works perfectly for customers and returns a challenge page to the reviewer, so the failure is invisible from the brand's side.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Allowlist datacentre traffic for the policy and opt-in pages, or lower the bot-protection level on them. Confirm the pages load from a cloud IP, not just from your office.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-018",
      "slug": "web-018",
      "title": "The site must be readable in English",
      "statement": "The website must be renderable in English, or an English version must be supplied.",
      "rationale": "US 10DLC reviewers assess against US requirements in English. A site with no English version cannot be evaluated for the disclosures and business description the registration depends on.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Provide an English version of the site, or at minimum English pages for the business description, privacy policy, and SMS terms.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-019",
      "slug": "web-019",
      "title": "The site must stay live for the whole vetting window, not just at submission",
      "statement": "The brand website must remain live and publicly reachable for the entire vetting period, which runs weeks after submission.",
      "rationale": "Vetting re-fetches the site days or weeks later, so a site that was up when the form was filled in is not the thing being judged. The named failure is a password-protected storefront: a shop put into maintenance mode for a redesign, or a trial plan that lapses, takes the brand down with it and the rejection arrives with no hint that timing was the cause.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website over the review window",
      "severity": "HIGH",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Klaviyo",
        "Postscript"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Keep the site public and unchanged until the brand and campaign are approved. Do not schedule a migration, a password gate or a plan downgrade during the window; AWS states 10DLC review takes at least four to six weeks and toll-free three to ten business days.",
      "notes": "Nothing at submission time can settle this — the failure happens after we stop looking. Surfaced as a warning with the deadline: the user has to hold the site up, and to hold off any redesign or hosting change, until approval lands.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is any redesign, migration, password gate or hosting downgrade scheduled between now and approval?",
        "howToCheck": [
          "Ask whoever runs the site what is planned for the next six weeks. AWS states 10DLC review takes at least four to six weeks and toll-free three to ten business days.",
          "Hold the site public and unchanged until the brand and campaign are approved.",
          "Check any trial plan or hosting subscription that could lapse mid-review."
        ],
        "failureLooksLike": "A shop is put into maintenance mode for a redesign three weeks after submission. Vetting re-fetches the site, finds a password-protected storefront, and the rejection gives no hint that timing was the cause."
      }
    },
    {
      "id": "WEB-021",
      "slug": "web-021",
      "title": "The site must not be behind a login or access wall",
      "statement": "The brand website must be publicly reachable without authentication.",
      "rationale": "A reviewer with no account cannot assess a site they cannot open, so an SSO or password wall is functionally identical to a site that does not exist. This is a distinct code from a broken site because the fix is different: expose a public surface rather than repair anything.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30921",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish a public marketing site describing the business, even where the product itself sits behind a login. Where the opt-in genuinely lives inside the authenticated area, supply a screenshot of it instead.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-022",
      "slug": "web-022",
      "title": "A login-gated product needs a public description page",
      "statement": "Where the brand's service genuinely sits behind a login, a public page describing the business and its messaging programme is required.",
      "rationale": "Plenty of legitimate businesses are entirely authenticated products, and rejecting them outright would be wrong. The requirement is a public surface a reviewer can read — not that the product itself be open.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish a public marketing or about page describing what the business does, who it serves, and what the SMS programme sends, and register that URL as the brand website.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-024",
      "slug": "web-024",
      "title": "The site must not look inauthentic",
      "statement": "A site judged to be a shell, an unmodified template, scraped or fabricated content, or stock-imagery-only is rejected.",
      "rationale": "Fraudulent registrations stand up a plausible-looking site to satisfy the website requirement, so reviewers learned to judge authenticity rather than mere existence. This is distinct from a site being incomplete: an unmodified theme demo with lorem text fails even though every required page exists.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish real content: actual products or services with real descriptions and prices, a real About page, real contact details, and photography of the actual business.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-026",
      "slug": "web-026",
      "title": "The site must look established rather than erected for the registration",
      "statement": "The website should show signs of being an operating business, not a shell created to pass vetting.",
      "rationale": "Reviewers weigh apparent age and depth because a site built the week before registration is the standard fraud pattern. Thin, brand-new sites are not automatically rejected but attract manual review and slower vetting.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish real depth before registering: products or services with detail, an about page, contact information, and any customer-facing content that shows the business operating.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-027",
      "slug": "web-027",
      "title": "The site must be more than a bare lead-capture form",
      "statement": "A URL resolving to a standalone lead form with no surrounding business site is rejected.",
      "rationale": "A page whose only content is a form collecting a phone number is the archetypal lead-generation surface, and lead generation is precisely what the consent-transfer rules prohibit. Reviewers therefore treat a bare form as evidence of the business model rather than as a thin website.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30920",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Point the brand website at your full site — home, about, contact, and product or service pages — rather than at the campaign landing page. Keep the landing page for the message flow field.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-028",
      "slug": "web-028",
      "title": "A campaign landing page is not the brand website",
      "statement": "Where the business runs a main site, that site must be registered as the brand website rather than a single campaign landing page.",
      "rationale": "A landing page is written to sell one thing, so it describes an offer rather than a business — and the reviewer needs the business. This is distinct from a bare lead form: the page can be perfectly substantial and still be the wrong page, which is why the fix is to change the URL rather than to build anything.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website vs the discoverable main site",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30920",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the main business domain in brand.website and keep the landing page where it belongs, in the message flow as the opt-in URL. Done when brand.website opens a site with navigation to the rest of the business.",
      "example": "website: https://acmecoffee.com · message flow opt-in URL: https://acmecoffee.com/rewards-signup",
      "notes": "Different fix from WEB-027, which rejects a bare lead-capture form with no business behind it at all. Here the business exists and the wrong URL was submitted.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-030",
      "slug": "web-030",
      "title": "E-commerce brands need a published catalog",
      "statement": "An e-commerce brand must show a real, populated product catalog rather than a placeholder store.",
      "rationale": "An empty or demo store is the e-commerce version of a shell site, and it directly contradicts a campaign describing order and promotional messaging. There are no orders to notify anyone about.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "verticals": [
          "RETAIL"
        ]
      },
      "applicabilityText": "Applies when the vertical is RETAIL.",
      "universal": false,
      "remediation": "Publish the real catalog with products, prices and descriptions before registering.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-031",
      "slug": "web-031",
      "title": "The business name on the site must match the registered brand",
      "statement": "The name displayed on the website must match the registered legal name or DBA.",
      "rationale": "The name match is how a reviewer connects the registration to the site, and it is the check that catches a reseller registering a client's traffic under the wrong entity. A mismatch with no stated relationship is treated as an identity failure rather than a branding choice.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Display the registered name prominently in the header, footer, or about page. Where the site trades under a different name, register that name as the DBA on the brand record.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-032",
      "slug": "web-032",
      "title": "The business name must be visible on the page, not only in the markup",
      "statement": "The business name or branding must be displayed in the logo, header or footer of the rendered page, not carried only in the title tag or metadata.",
      "rationale": "A reviewer decides whose site this is by looking at it for a few seconds. A name that lives only in the page title, or only in an image with no text near it, means the answer is \"nobody in particular\" — which is the impression a template site left unbranded also gives, and it is the one this rule exists to catch.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "rendered header, footer and logo region",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30492",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "TFV 1205",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the business name in the site header or footer as text, next to or instead of the logo image. Done when the name is legible in a screenshot of the top of the page without opening the source.",
      "notes": "Whether the name is present in the text at all is WEB-031, which reads the body. This rule is about prominence, which only the rendered page shows — hence VISION.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-034",
      "slug": "web-034",
      "title": "The site must describe the business and the messaging programme",
      "statement": "The website must carry enough information about the business, and ideally the messaging programme, for a reviewer to understand what is being registered.",
      "rationale": "Reviewers cross-check the site against the campaign, so a site that never says what the business does leaves nothing to check against. Twilio codes this separately from \"site is a bare form\" because a site can be substantial and still never explain the business.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "AWS"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30919",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a clear description of what the business does and who it serves, plus a page describing the SMS programme and linking to the SMS terms.",
      "catalogIds": [
        "WEB-029",
        "WEB-042"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-035",
      "slug": "web-035",
      "title": "The reseller platform website must not stand in for the customer site",
      "statement": "An ISV or reseller must not submit its own platform website in place of the end customer's.",
      "rationale": "This is the website-side twin of registering the wrong entity. It looks harmless — the platform site is real and substantial — but it means the brand being vetted is not the business that will send the messages.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website + brand.company_name",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Submit the end customer's own website. Your platform belongs on the CSP record, never on the brand.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-037",
      "slug": "web-037",
      "title": "The site, the registered name and the registered address must agree",
      "statement": "The business described on the website, the registered company name and the registered postal address must corroborate one another.",
      "rationale": "Verification works by finding the same business in two independent places, so a site that names a different company, or shows an address in another state, leaves the reviewer with two businesses and no way to join them. The honest version of this is common: the registration carries the legal entity and the accountant's address while the site carries the trading name and the shop, and nothing on either says they are the same people.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "website content vs brand.company_name + brand address",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the legal entity name and the registered address somewhere on the site — the footer, the contact page or the terms — even where the business trades under a shorter name. Done when a stranger reading the site can find both values exactly as the registration states them.",
      "pitfalls": [
        "A registered agent or accountant address on the brand record and the shop address on the site is a mismatch even though both are true. Publish both on the site, or register the one the site shows."
      ],
      "notes": "The catalog scopes this to the toll-free verification path, where AWS applies it as the business-verification standard. It is authored universally because the same corroboration is what every vetting path performs; where a provider does not apply it, satisfying it costs nothing.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-039",
      "slug": "web-039",
      "title": "Contact details on the site must match the brand record",
      "statement": "The support phone and contact shown on the website should match the brand registration.",
      "rationale": "Corroboration between site and registration is what turns two weak signals into one strong one. Divergent contact details are a common sign that the registration was assembled from someone else's business information.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + brand.phone + brand.email",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the contact details on the site and on the registration the same, or explain the difference (for example a separate support line) somewhere visible.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-043",
      "slug": "web-043",
      "title": "The site must have an About page and a Contact page",
      "statement": "An About page and a Contact page must exist on the brand domain.",
      "rationale": "These two pages are where a reviewer goes first, because between them they answer who this is and how to reach them — the two questions the whole website requirement exists to settle. A site that answers both somewhere in a scrolling home page still fails the reviewer's habit of looking for them in the navigation.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "site navigation",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Plivo",
        "TCR",
        "MessageIQ"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30919",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add an About page describing the business and a Contact page carrying an address, a phone number and an email, and link both from the site navigation. Done when both are reachable in one click from the home page.",
      "notes": "Absorbs POL-039, which states the same page inventory and adds the privacy policy and terms — those two are already required by WEB-045 and WEB-046. Severity divergence in the catalog: Plivo lists the missing pages among its rejection reasons while the row itself is graded MEDIUM; graded HIGH here because two of the four pages it names are separately BLOCKING.",
      "catalogIds": [
        "POL-039"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-044",
      "slug": "web-044",
      "title": "The footer should carry a phone number, an email and a street address",
      "statement": "The site footer should show a business phone number, an email address and a physical street address rather than a PO box.",
      "rationale": "The footer is the one place a reviewer can check on every page without navigating, so a business that puts all three there answers the corroboration question wherever the crawl happens to land. This is the stricter, conjunctive form of the contact requirement, and the street address is the element most often missing — a PO box proves a mailbox was rented, not that anyone works anywhere.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "site footer",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add all three to the site footer template so they appear on every page, using the same values as the brand record. Done when the footer of an interior page carries a street address, a phone number and an email.",
      "notes": "Single secondary source in the catalog, flagged unverified, and the \"not a PO box\" element in particular has no carrier code behind it — hence MEDIUM and worded as an expectation. The weaker, disjunctive requirement that contact information appear somewhere is WEB-047, which is the one that fails a site outright.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-045",
      "slug": "web-045",
      "title": "A privacy policy must be reachable from the website",
      "statement": "The brand website must link to a reachable privacy policy.",
      "rationale": "Reviewers navigate from the site to the policy rather than trusting the URL field alone, and a policy that exists but is unlinked reads as one created solely for the registration. CTIA also requires it to be reachable from the call to action.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the privacy policy from the site footer and from the opt-in surface.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-046",
      "slug": "web-046",
      "title": "Terms must be reachable from the website",
      "statement": "The brand website must link to reachable Terms & Conditions or SMS Terms.",
      "rationale": "The terms are where the messaging programme is documented for consumers, so an unlinked terms page is unreachable by the people it exists to inform. Reviewers check the path a real consumer would take.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the terms (or a dedicated SMS terms page) from the site footer and from the opt-in surface.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-047",
      "slug": "web-047",
      "title": "The site must show business contact information",
      "statement": "The website must display contact details — an address, phone number, or support email identifying the business.",
      "rationale": "Contact details are how a reviewer corroborates that the business is real and matches the brand record, and CTIA separately requires that a message URL and its destinations identify the owner with contact information. A site with no way to reach anyone reads as a front.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish a contact page with the business address, a phone number, and a support email on the brand domain, and check they match the brand registration.",
      "catalogIds": [
        "WEB-041"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-054",
      "slug": "web-054",
      "title": "A brand with a website must publish its policies as web pages, not as PDFs",
      "statement": "Where the brand has a website, the privacy policy and terms URLs must serve an HTML page rather than a PDF or other downloadable file.",
      "rationale": "Vetters read policy pages with a text fetcher, and a PDF frequently comes back as an empty body — so a complete, correct policy is scored as a missing one. It also breaks the consumer path the rules exist to protect: a phone that downloads a file instead of showing a page is a dead end at exactly the moment someone is deciding whether to consent.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "Content-Type of privacyPolicyUrl and termsOfServiceUrl",
      "severity": "MEDIUM",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "7102",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Publish the policy as an HTML page on the brand domain and submit that URL; keep the PDF as an extra download if you want one. Done when the policy URL returns text/html.",
      "example": "privacy_policy_url: https://acmecoffee.com/privacy (text/html), not https://acmecoffee.com/files/privacy.pdf",
      "pitfalls": [
        "Uploading a PDF is the sanctioned route for a brand with NO website — this rule only fires when a site exists, and swapping a working page for a file to satisfy a provider that accepts uploads makes things worse."
      ],
      "notes": "Absorbs POL-019, the same warning stated from the policy-page side. The catalog marks the underlying source unverified — inferred from Twilio 7102 rather than published as a rule — so it is graded MEDIUM and worded as an expectation rather than a refusal.",
      "catalogIds": [
        "POL-019"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-055",
      "slug": "web-055",
      "title": "The site should describe the SMS programme",
      "statement": "The website should carry information about what the messaging programme sends, to whom, and why.",
      "rationale": "A programme described only inside the registration exists only for the reviewer. Documenting it publicly is both a CTIA expectation and the thing that lets a consumer verify the messages they receive are legitimate.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add a short SMS programme section to the site or the SMS terms page: what you send, how often, who it is for, and how to stop.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-056",
      "slug": "web-056",
      "title": "Opt-in and opt-out information must be clear on the site",
      "statement": "The website must display how to opt in and how to opt out.",
      "rationale": "A consumer who wants to stop should not have to wait for the next message to find out how. Publishing both routes on the site is a CTIA expectation and materially reduces the complaint rate that drives carrier filtering.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "State on the site how to join and how to leave, naming the keywords: \"Reply STOP to any message to unsubscribe.\"",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-057",
      "slug": "web-057",
      "title": "Website content must be consistent with the declared use case",
      "statement": "What the website shows the business doing must be consistent with the campaign use case.",
      "rationale": "The use case determines the consent standard and throughput a campaign receives, so reviewers verify it against the business rather than accepting the selection. A site that is plainly an e-commerce store behind a 2FA-only registration reads as an attempt to obtain transactional treatment for marketing traffic.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Choose the use case that matches what the site shows the business doing. Where you genuinely send several categories, use MIXED and collect promotional-grade consent.",
      "example": "A retail site sending promotions and order updates → MIXED, not ACCOUNT_NOTIFICATION.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-058",
      "slug": "web-058",
      "title": "Website, description, flow and samples must identify one sender",
      "statement": "The website, campaign description, message flow and sample messages must all identify the same sending business.",
      "rationale": "Each artifact is written separately, so a single inconsistent name is common and innocent — but a reviewer cannot distinguish that from a registration assembled out of several businesses. Consistency across all four is what makes the sender verifiable.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign fields + samples",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Use one customer-facing name everywhere, and make it the registered brand display name or DBA.",
      "example": "Website header, description, message flow and every sample all say \"Acme Coffee\".",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-059",
      "slug": "web-059",
      "title": "The site referenced in the message flow must be the registered brand site",
      "statement": "The website referenced inside the message flow must belong to the same business as the registered brand website.",
      "rationale": "A message flow pointing at a different domain usually means consent is collected on a partner or platform property, which raises the consent-transfer question directly. Where it is legitimate, it needs explaining rather than leaving for a reviewer to infer.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "campaign.message_flow + brand.website",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reference the brand's own domain in the message flow. Where consent is collected on a third-party platform, say so explicitly and explain the relationship.",
      "example": "Brand website acmecoffee.com; message flow references acmecoffee.com/signup.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-060",
      "slug": "web-060",
      "title": "Declared attributes must be consistent with the crawled site",
      "statement": "The declared campaign attributes must be consistent with what the website shows, not only internally consistent with the form.",
      "rationale": "Attributes are self-declared, so the website is the only independent evidence available. A site showing age-restricted products behind a campaign declaring no age gating is a contradiction the form alone can never reveal.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign attributes",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reconcile each attribute against the website: age-gated products, lending offers, links and phone numbers in messaging.",
      "example": "Site sells age-restricted products → age gated: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-062",
      "slug": "web-062",
      "title": "An undeclared additional use case must be surfaced",
      "statement": "Where the website or description implies a message category the campaign has not declared, it must be flagged.",
      "rationale": "Live traffic must match the registered use case, and an undeclared category is a violation waiting to happen the first time that content is sent. Catching it at registration is the difference between adding a use case and being filtered later.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign.description",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Sinch"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Declare every category you will send, or remove the undeclared one from your plans. MIXED covers promotional plus transactional.",
      "example": "Site shows cart-abandonment flows → declare MARKETING (or MIXED), not ACCOUNT_NOTIFICATION alone.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-063",
      "slug": "web-063",
      "title": "Charitable solicitation on the site forces the charity use case",
      "statement": "Where the website, description, or samples solicit donations, the charity use case must be used.",
      "rationale": "Donation solicitation carries eligibility requirements — verified 501(c)(3) status — that exist precisely to stop fraudulent charity messaging. Running it under a marketing use case bypasses that check, so reviewers force the correction.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Infobip"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register under the CHARITY use case with 501(c)(3) status declared on the brand, or remove the donation ask from both the campaign and the messaging programme.",
      "example": "Entity type: NON_PROFIT · Tax exempt status: 501(c)(3) · Use case: CHARITY",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-064",
      "slug": "web-064",
      "title": "Political content on the site forces the political use case",
      "statement": "Where the website, description, or samples carry political-campaign content, the political use case must be used.",
      "rationale": "Political messaging requires external vetting recognised by both major carriers, and that requirement is meaningless if the category can be avoided by choosing a different label. Reviewers therefore assess the content, not the selection.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "AT&T",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register under POLITICAL on a NON_PROFIT brand with political vetting imported, or remove political content from the programme entirely.",
      "example": "Use case: POLITICAL, on a NON_PROFIT brand with Campaign Verify vetting.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-066",
      "slug": "web-066",
      "title": "Lending content anywhere requires the direct-lending declaration",
      "statement": "Where lending content appears in the description, the message flow, or the website, directLending must be declared.",
      "rationale": "The attribute exists so carriers can apply the right scrutiny to credit messaging, and it is assessed against the website as well as the form. Undeclared lending is treated as concealment rather than oversight, which is a much worse posture than declaring it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + campaign.direct_lending",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the direct-lending attribute true when the business offers credit of any kind. Declaring it is a fixable attribute question; concealing it is a business-model rejection.",
      "example": "Site offers financing → direct lending: true.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-068",
      "slug": "web-068",
      "title": "A cart-reminder programme must say so in the opt-in terms on the site",
      "statement": "Where the programme sends shopping-cart reminders, the opt-in terms shown at the call to action must disclose it.",
      "rationale": "A cart reminder is a message about something the consumer did not finish doing, sent because their behaviour was tracked — so T-Mobile requires it to be disclosed where consent is given rather than discovered when the first one arrives. Retailers add abandoned-cart flows through a platform months after the checkbox copy was written, and nothing in that flow prompts anyone to go back and update it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "opt-in disclosure text on the site",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "T-Mobile",
          "code": "8003",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add cart reminders to the message types named in the opt-in disclosure beside the consent control. Done when the text at the checkbox names them alongside whatever else the programme sends.",
      "pitfalls": [
        "Updating the SMS terms page is not enough on its own — T-Mobile asks for the disclosure at the call to action, which is the checkbox copy, and POL-109 separately asks the privacy policy to say how abandonment is detected."
      ],
      "notes": "Conditional on cart-reminder traffic being in scope, which no applicability dimension expresses — there is no cart-reminder attribute. The condition is carried in the criteria, which pass immediately when the programme sends no cart reminders.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-069",
      "slug": "web-069",
      "title": "The described opt-in and the live opt-in must both pass, separately",
      "statement": "The call to action as described in the registration and the call to action as it appears live on the website must be audited as two separate passes.",
      "rationale": "Ranked the #2 most-missed check in the catalog. A brand can describe a perfect opt-in that does not exist on the site, or run a compliant opt-in and describe it badly — those are different failures with different fixes, and collapsing them into one verdict hides whichever one is broken. Reviewers genuinely do both passes.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "campaign.message_flow + live website opt-in",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reconcile the two: make the live page carry everything the description claims, and make the description match what the page actually shows. Where they diverge, fix the page first — the page is what the consumer saw.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-070",
      "slug": "web-070",
      "title": "The call to action described in the registration must exist on the site",
      "statement": "Where consent is collected on the site, the call to action the registration describes must be findable on the crawled site.",
      "rationale": "A described opt-in that cannot be found reads to a reviewer as consent that does not exist, which is the strongest possible reason to refuse a campaign — and it is usually true by accident: the page moved in a redesign, the form sits behind an interaction the crawler never triggers, or the description was written from the plan rather than from the site. The consumer-side stake is the same one: an opt-in nobody can locate is one nobody can audit.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "live site vs campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Zoom"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30891",
          "remediable": true,
          "generation": "gen1"
        }
      ],
      "applicability": {
        "consentMethods": [
          "web_form",
          "checkout",
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by web form, checkout and QR code.",
      "universal": false,
      "remediation": "Give the exact URL of the page holding the consent control in the message flow, and confirm the control is present in the page source rather than injected after an interaction. Done when opening that URL in a private window shows the opt-in without clicking anything.",
      "pitfalls": [
        "A consent box that only appears after a product is added to the cart is invisible to a crawler that never adds one. Supply a screenshot of the checkout step as well, or the reviewer sees a page with no opt-in on it."
      ],
      "notes": "The twin of WEB-069, which audits the described and live opt-ins as two passes. This rule is the narrower question underneath it: does the described surface exist at all.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-072",
      "slug": "web-072",
      "title": "A verbal-only programme should not point at a website with no consent surface",
      "statement": "Where verbal opt-in is the only declared consent method, the registration must not supply a website opt-in URL that shows no SMS consent surface.",
      "rationale": "A reviewer given a URL looks for the opt-in on it, finds nothing, and records that the consent surface could not be verified — which is a worse outcome than the honest one, where a phone script is the evidence and the site was never offered as such. Brands do this by reflex because the form asks for a URL, so they paste the home page.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website vs the declared consent method",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "AWS"
      ],
      "applicability": {
        "consentMethods": [
          "verbal_live",
          "verbal_ivr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by live verbal and IVR.",
      "universal": false,
      "remediation": "Describe the call in the message flow and attach the script or recording as the evidence, rather than pointing at a page that does not collect consent. Keep the website on the brand record, where it belongs — this rule is about the opt-in URL only.",
      "example": "message flow: \"Consent is captured verbally during the support call; the agent reads the disclosure from the attached script.\" No opt-in URL supplied.",
      "notes": "Flagged unverified in the catalog — an inference from the AWS UNCLEAR_OPT_IN reason rather than a published rule. Tagged to the two verbal methods, so a brand that also collects consent on the web is never asked about it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-073",
      "slug": "web-073",
      "title": "The brand website must serve a certificate that verifies",
      "statement": "The brand website must present a valid TLS chain — not expired, not self-signed, and issued for the hostname actually submitted.",
      "rationale": "A vetting crawler stops at a certificate error and never reads a word of the site, so the rejection arrives as \"the website could not be verified\" rather than as anything about a certificate. Browsers hide this: the people who work at the business have been clicking through the warning for months, and a certificate valid for the bare domain but not for the www form fails only for whoever submitted the other one.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website TLS chain",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Bandwidth",
        "TCR",
        "Twilio",
        "Plivo"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "1103",
          "remediable": true
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "2103",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Reissue the certificate for the exact hostname in brand.website, covering both the www and bare-domain forms, and renew before expiry. Done when an external SSL checker — not your own browser — reports a complete, valid chain for the URL you are about to submit.",
      "pitfalls": [
        "A certificate valid for acmecoffee.com and not for www.acmecoffee.com fails whenever the submitted URL uses the other form. Submit the form the certificate actually covers.",
        "An expired intermediate is invisible in Chrome, which caches the issuer, and fatal to a strict crawler that does not. Test from outside your own network."
      ],
      "notes": "Absorbs BRD-128, which states the same certificate requirement from the brand-record side. Severity divergence in the catalog: website-evidence-012 and CARR-008 grade a TLS failure HIGH; Bandwidth, Plivo and Twilio grade it BLOCKING. Strictest kept.",
      "catalogIds": [
        "BRD-128"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-074",
      "slug": "web-074",
      "title": "Pages must not load sub-resources over HTTP",
      "statement": "A page must not load sub-resources over, or redirect through, unencrypted HTTP — mixed content fails.",
      "rationale": "Mixed content produces browser warnings on the exact page where a consumer is about to hand over a phone number, which undermines the trust the whole framework is built on. It also frequently signals an unmaintained site.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "opt-in landing page",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Serve every asset over https and remove any http:// redirect hop in the chain to the opt-in page.",
      "notes": "Not decidable from a markdown-only fetch — needs the raw HTML and the sub-resource list. Recorded so the crawler gap is visible rather than silently unchecked.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-075",
      "slug": "web-075",
      "title": "Submitted URLs must not redirect",
      "statement": "A URL submitted to TCR must resolve directly rather than returning a 30x redirect.",
      "rationale": "TCR forbids redirection on fetched URLs. This is deeply counter-intuitive because the link works perfectly in a browser — including the http-to-https upgrade almost every site performs — so brands submit a URL they have personally tested and are rejected anyway.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website + campaign URLs",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Submit the canonical URL you land on after all redirects resolve — with https, the final host (www or bare), and the final path.",
      "example": "Submit https://www.acmecoffee.com/signup rather than http://acmecoffee.com/signup",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-077",
      "slug": "web-077",
      "title": "The website must not redirect to an unrelated brand",
      "statement": "A brand website that redirects to a business other than the registered brand is rejected, whatever the submitted URL says.",
      "rationale": "A domain that forwards to somebody else is the signature of a registration assembled from a bought or borrowed domain, and it is also what happens innocently when a business is acquired and its old domain is pointed at the new owner. Reviewers cannot tell those apart from the outside, so the one that gets explained on the registration is the one that survives.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website final host + landing content",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "TCR",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30907",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "603",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register the domain the business actually trades under. Where the redirect is deliberate — a rebrand, an acquisition, a franchisor site — say so in the campaign description and make the destination state the relationship in visible page content.",
      "notes": "Absorbs BRD-131. The ambiguous case named in the catalog is a redirect to a parent company or franchisor: legitimate, and indistinguishable from a hijack unless the destination says so on the page.",
      "catalogIds": [
        "BRD-131"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-079",
      "slug": "web-079",
      "title": "A redirected URL is reviewed at its destination, not where it was submitted",
      "statement": "Where a submitted URL redirects off the registered domain, the full content review applies to the destination, so the destination is what the registration must be able to survive.",
      "rationale": "Reviewers follow the chain and judge whatever they land on, which means a brand can be assessed against a site it did not submit and does not control — a marketing shortener, an agency landing page, a marketplace storefront. The business believes its own site was reviewed and cannot understand the finding, because nothing in the rejection names the page that produced it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website redirect chain",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "CTIA",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register the URL the chain actually ends on, or remove the off-domain hop so the submitted address serves the content directly. Done when brand.website and the final URL of the fetch are the same registrable domain.",
      "example": "brand.website: https://acmecoffee.com — fetch ends on acmecoffee.com, not on a link-shortener or an agency landing page.",
      "pitfalls": [
        "Pointing the registration at the destination fixes this and can still fail WEB-077 if the destination belongs to a different business — moving the URL does not change whose site is being judged."
      ],
      "notes": "A scoping rule: it changes what every other content check reads. Fenced against its two neighbours — WEB-075 fails any redirect at all because TCR forbids 30x on a fetched URL, and WEB-077 judges whether the destination is an unrelated brand. This one is the mechanical fact that the destination is a different domain, which is what makes the other two matter.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-081",
      "slug": "web-081",
      "title": "A newly registered domain is scored as higher risk",
      "statement": "A recently registered, disposable or short-lived domain raises the risk score on the brand, independently of what the site contains.",
      "rationale": "Fraudulent registrations need a domain, and the cheapest one is bought the same week. No provider publishes a threshold, so this is scored rather than refused — which is worse to be on the wrong side of than a rejection, because the brand verifies and then quietly underperforms with no finding to point at. A genuinely new business is in exactly the same position and can do nothing about it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website domain registration date",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30961",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the business owns an older domain, register that one. Where it does not, expect slower vetting and stronger scrutiny of everything else, and make the rest of the registration corroborate the business as heavily as it can — matching contact details, a published address, an active social presence.",
      "notes": "Absorbs WEB-086, which states the same signal together with its remediability: no form edit changes a domain's age. We run no WHOIS lookup, so the user is the one who has to know how old their domain is — and if it is weeks old, to plan for a slower vet rather than resubmitting into the same score.",
      "catalogIds": [
        "WEB-086"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "How old is this domain — and if it is only weeks old, have you planned for a slower vet rather than a resubmission?",
        "howToCheck": [
          "Check the registration date in your registrar account or a public WHOIS lookup.",
          "Where the business owns an older domain, register that one instead.",
          "Where it does not, make the rest of the record corroborate the business as heavily as it can: matching contact details, a published address, an active social presence."
        ],
        "failureLooksLike": "Nothing is refused. The brand verifies and quietly underperforms, and resubmitting an unchanged registration produces the same score — no form edit changes a domain's age."
      }
    },
    {
      "id": "WEB-083",
      "slug": "web-083",
      "title": "Hosting shared with malicious neighbours is scored against the domain",
      "statement": "A domain resolving to an IP or ASN shared with known-malicious sites carries that reputation into the brand score.",
      "rationale": "Reputation is assessed at the address as well as the name, so a business on cheap shared hosting inherits whatever its neighbours did. Nothing about the business causes it and nothing on the site reveals it, which makes it one of the hardest findings to act on — the brand looks clean from every angle the operator can see.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "resolved IP and ASN neighbours",
      "severity": "MEDIUM",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30961",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where vetting repeatedly stalls with no finding on the content, move the site to a dedicated IP or a reputable host and re-submit. Done when the domain resolves to an address whose other tenants you can account for.",
      "notes": "We resolve no addresses and hold no reputation feed. The user can check this themselves with a reverse-IP lookup on their domain: if it shares an address with hundreds of unrelated sites, that is the signal, and changing host is the only fix.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does this domain share its IP address with sites you cannot account for?",
        "howToCheck": [
          "Run a reverse-IP lookup on the domain.",
          "Hundreds of unrelated sites on the same address is the signal — cheap shared hosting inherits whatever its neighbours did.",
          "The only fix is moving to a dedicated IP or a reputable host, then resubmitting."
        ],
        "failureLooksLike": "Vetting stalls repeatedly with no finding on the content. Nothing about the business caused it and nothing on the site reveals it, so the brand looks clean from every angle the operator can see."
      }
    },
    {
      "id": "WEB-084",
      "slug": "web-084",
      "title": "A lookalike domain is treated as a phishing signal",
      "statement": "A domain that imitates a better-known brand through misspelling, homoglyphs or an added word is screened as a phishing indicator.",
      "rationale": "Impersonation domains are the standard vehicle for credential phishing over SMS, so screening the name itself is cheap and catches attacks before any content exists to judge. Legitimate businesses collide with it innocently — a reseller whose domain contains the manufacturer's name, a regional franchise — and the rejection reads as an accusation rather than a score.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website domain string",
      "severity": "HIGH",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30960",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the domain contains another company's brand, be able to show the relationship — an authorised-reseller page, a franchise agreement named on the site. Where it does not, expect a slower vet and make the brand record and the site agree on the business name exactly.",
      "notes": "The catalog flags this as unverified: no primary source names homoglyph domains as a rule, and the nearest hooks are Twilio's obfuscated-URL language and codes 30960/30961. Recorded as a heuristic, not as a quoted requirement. We run no similarity screen, so the user should judge their own domain against the brands it resembles.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does this domain contain, or closely resemble, another company's brand name?",
        "howToCheck": [
          "Read the domain as a stranger would and name the brand it resembles — misspellings, homoglyphs and an added word all count.",
          "Where the resemblance is a real relationship, publish it: an authorised-reseller page or a franchise agreement named on the site.",
          "Where it is coincidence, expect a slower vet and make the brand record and the site agree on the business name exactly."
        ],
        "failureLooksLike": "A reseller whose domain carries the manufacturer's name is screened as a phishing indicator, and the rejection reads as an accusation rather than as a score."
      }
    },
    {
      "id": "WEB-085",
      "slug": "web-085",
      "title": "Adverse reputation on the domain blocks the brand",
      "statement": "Third-party reputation signals on the domain — prior compromise, complaint volume, adverse media, blocklist entries — block the registration.",
      "rationale": "Carriers buy reputation data and apply it before anyone reads the site, so a domain that was compromised two owners ago arrives already refused. The business has no visibility into which feed flagged it or why, and the rejection names none of that, which is why brands re-submit unchanged registrations several times before anyone tells them the domain is the problem.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website domain reputation",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "TFV 1604",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "TFV 1609",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Check the domain against the public blocklists and safe-browsing services before registering, and clear whatever they report — a compromised page, an old redirect, a listing from a previous owner. Where the history belongs to a previous owner, expect to have to appeal with evidence of the transfer.",
      "notes": "Absorbs WEB-082, prior compromise being one of the signals the same lookup returns. We query no reputation source. The user has to run this themselves — Google Safe Browsing, VirusTotal and the major blocklists are the ones the vendors draw on — and to do it before submitting, because a blocked domain burns the registration rather than pausing it.",
      "catalogIds": [
        "WEB-082"
      ],
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Is this domain clean on the public blocklists and safe-browsing services today?",
        "howToCheck": [
          "Check it against Google Safe Browsing, VirusTotal and the major blocklists — these are the feeds the vendors draw on.",
          "Clear whatever they report: a compromised page, an old redirect, a listing left by a previous owner.",
          "Where the history belongs to a previous owner, have evidence of the transfer ready for an appeal."
        ],
        "failureLooksLike": "A domain compromised two owners ago arrives already refused. The rejection names no feed and no reason, so unchanged registrations are submitted several times before anyone mentions the domain."
      }
    },
    {
      "id": "WEB-090",
      "slug": "web-090",
      "title": "The message flow must deep-link the opt-in page, not the homepage",
      "statement": "The URL given in the message flow must point at the page carrying the consent surface, not at the site root.",
      "rationale": "A reviewer given a homepage has to hunt for the opt-in, and if they do not find it quickly they reject. Deep-linking converts a search into a single click, and it is one of the cheapest possible improvements to approval odds.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "campaign.message_flow",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Bandwidth",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace the homepage link in the message flow with the direct URL of the page holding the consent checkbox.",
      "example": "https://acmecoffee.com/signup rather than https://acmecoffee.com",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-098",
      "slug": "web-098",
      "title": "The QR landing page must be publicly reachable",
      "statement": "The page a QR code opens must be fetchable by anyone — no login, geo-gate, app-install wall or captive portal in front of it.",
      "rationale": "A QR code is scanned in a shop, on a table, at an event — and if the page behind it demands an app or a login, the consumer never opts in and the reviewer never sees the surface either. The captive-portal case is the one that catches careful operators: a code that works perfectly on the store Wi-Fi opens nothing at all for someone on a mobile network, and nobody tests it that way.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "QR destination fetch",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bird"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30909",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "804",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Host the QR destination on a public https page on the brand domain, with no login, no app interstitial and no dependence on the venue network. Done when scanning the code on a phone with mobile data and no app installed shows the opt-in.",
      "pitfalls": [
        "Test the printed code, not the URL. A code printed from a draft, or one pointing at a short link whose account has lapsed, opens something entirely different from what was designed."
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-101",
      "slug": "web-101",
      "title": "The QR landing page must display the registered brand name",
      "statement": "The page a QR code opens must display the brand name as registered, so the consumer can see whose programme they are joining.",
      "rationale": "A QR code is opaque by design: the consumer has no idea where it goes until the page loads, so the page is the first and only chance to say who is asking for their number. An unbranded hosted form — the platform's template with the merchant name left at its default — gives them nothing to recognise, and gives the reviewer nothing to tie the surface to the brand.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "QR landing page branding",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Aerialink",
          "code": "806",
          "remediable": true
        }
      ],
      "applicability": {
        "consentMethods": [
          "qr"
        ]
      },
      "applicabilityText": "Applies when consent was collected by QR code.",
      "universal": false,
      "remediation": "Put the registered brand name or DBA in the heading of the landing page, not only in the page title or the platform footer. Done when someone who has just scanned the code can see whose programme it is without scrolling.",
      "notes": "Distinct from WEB-102, which asks whether the landing page carries the full disclosure set. A page can carry every required sentence and still never say who is sending.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-107",
      "slug": "web-107",
      "title": "Age-restricted content on the site must sit behind a working age gate",
      "statement": "Where the site or the campaign carries age-restricted content, the site must present a functioning age gate in front of it.",
      "rationale": "The gate is what keeps a minor from reaching alcohol, tobacco, firearms or adult material through a link in a text message, and carriers charge per-message violation fees rather than merely rejecting, so the cost of getting it wrong keeps accruing. Businesses that gate their checkout often leave the marketing pages open, which is the version of this a compliance-minded operator is most likely to trip.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "age-gate interstitial on the site",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL",
        "HUMAN"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "T-Mobile",
        "Twilio",
        "AWS",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "T-Mobile",
          "code": "706",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put a date-of-birth gate in front of every page carrying restricted content, enforced server-side so a direct link cannot skip it. Done when opening a product URL in a private window shows the gate before the product.",
      "pitfalls": [
        "Gating the checkout and leaving the catalog open fails: the reviewer opens a product page directly and never sees the gate."
      ],
      "notes": "Absorbs MSG-176, which states the requirement from the message-copy side, and BRD-280 from the brand-eligibility side. The gate's form is WEB-108 (a full date of birth), its position WEB-109, its threshold WEB-112, and whether it survives a determined visitor WEB-110 — that last one is why HUMAN is declared alongside CRAWL here: a crawl can see a gate and cannot tell whether it holds. The user has to try to bypass their own gate, by opening a restricted URL directly in a fresh browser, and confirm they are stopped.",
      "catalogIds": [
        "MSG-176",
        "BRD-280"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Opening a restricted product URL directly, in a fresh private window, are you stopped by the gate before you see the product?",
        "howToCheck": [
          "Copy a deep link to a restricted page — not the homepage — and open it in a private window.",
          "Confirm the gate appears before any restricted content is rendered.",
          "Repeat for the marketing and catalog pages, not just the checkout."
        ],
        "failureLooksLike": "The checkout is gated and the catalog is open. The reviewer opens a product page directly, never sees the gate, and carriers charge per-message violation fees rather than merely rejecting."
      }
    },
    {
      "id": "WEB-109",
      "slug": "web-109",
      "title": "The age gate must come before the restricted content, not after it",
      "statement": "The age gate must be presented before any age-restricted content is accessible, including on a page reached by a direct link.",
      "rationale": "A gate that appears after the visitor has already seen the product has protected nobody — the harm the rule addresses happened on page load. The usual cause is a gate wired into the home page template only, so every deep link from a message, a search result or a QR code lands past it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "page flow ordering",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Aerialink"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Enforce the gate on every restricted URL rather than on the home page, and check the answer server-side before the page renders. Done when pasting a product URL into a fresh browser shows the gate first.",
      "pitfalls": [
        "For a QR opt-in the date of birth must be captured before consent is collected, not on the confirmation step — a gate after the number has been submitted is too late for both requirements."
      ],
      "notes": "Conditional on age-restricted content being present, the same condition as WEB-107, and carried in the criteria for the same reason: tagging on the declared ageGated attribute would exempt exactly the brands that under-declare.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-110",
      "slug": "web-110",
      "title": "The age gate must not be trivially bypassable",
      "statement": "The age gate must not be defeatable by dismissing it, by loading a page directly, or by clearing a cookie.",
      "rationale": "A gate implemented in front-end code is decoration: it stops nobody who does not want to be stopped, and it stops the reviewer not at all, since the first thing they try is the direct URL. Businesses build it this way because that is what the plugin does, and because the version that actually holds requires the server to know the answer.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "age-gate implementation",
      "severity": "HIGH",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Enforce the gate server-side: refuse to render restricted pages until the age has been submitted and recorded for the session. Done when a direct link, a dismissed dialog and a cleared cookie all still land on the gate.",
      "notes": "Nothing we fetch can tell a real gate from a dismissible overlay, so this one is on the user to test: open a restricted product URL directly in a private window, press Escape on the dialog, and clear the cookie between attempts. If any of the three gets through, the gate is decoration and WEB-107 will be judged on it.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the gate still hold after a direct link, a dismissed dialog and a cleared cookie?",
        "howToCheck": [
          "Open a restricted URL directly in a private window.",
          "Press Escape on the dialog, and try to interact with the page behind it.",
          "Clear the cookie and reload. If any of the three gets through, the gate is decoration."
        ],
        "failureLooksLike": "A front-end plugin renders the page and covers it with an overlay. It stops nobody who does not want to be stopped, and it stops the reviewer not at all — the first thing they try is the direct URL."
      }
    },
    {
      "id": "WEB-111",
      "slug": "web-111",
      "title": "Alcohol delivery must gate the ordering flow, not just a splash page",
      "statement": "An alcohol delivery brand must present the age gate on the ordering flow itself, not only on a marketing splash page.",
      "rationale": "Delivery is the case where the gate has to hold, because the transaction ends with alcohol arriving at a door rather than with someone standing at a counter. A splash gate on the marketing site is satisfying to build and does nothing for the ordering app or the checkout subdomain, which is usually a different system entirely.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "checkout and ordering flow",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30956",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicability": {
        "verticals": [
          "alcohol_delivery"
        ]
      },
      "applicabilityText": "Applies when the vertical is alcohol_delivery.",
      "universal": false,
      "remediation": "Add the date-of-birth gate to the ordering flow — the cart, the checkout, and the app — as well as to the marketing site, and verify age again at handover. Done when the order path itself refuses an under-21 date of birth.",
      "notes": "Twilio scopes 30956 to \"all linked websites and opt-in flows\", which is what makes a splash-page-only gate insufficient here. Tagged to the alcohol_delivery vertical per the catalog condition; the general alcohol threshold rule is WEB-112 and applies whether or not delivery is involved.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-112",
      "slug": "web-112",
      "title": "An alcohol age gate must enforce 21, not 18",
      "statement": "Where the restricted content is alcohol, the age gate must enforce a 21+ threshold rather than 18+.",
      "rationale": "Twenty-one is the US drinking age, so an 18+ gate admits a whole cohort the rule exists to exclude and does it while looking compliant. The mistake travels with software: templates and plugins ship with 18 as the default because that is the threshold most of the world uses, and nobody changes it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "age-gate threshold",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30955",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Set the gate's minimum age to 21 wherever alcohol content is served, and check the plugin default rather than assuming it. Done when entering a date of birth for a twenty-year-old is refused.",
      "notes": "Conditional on alcohol content being present, carried in the criteria rather than as a tag — there is no alcohol attribute on the registration, and the declared ageGated flag says nothing about which substance is behind the gate.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-115",
      "slug": "web-115",
      "title": "A landing page reached from a message must publish a postal address",
      "statement": "Any landing site a message links to must publish contact information including a postal mailing address.",
      "rationale": "CTIA requires the destination of a bulk message to identify who is behind it, and a postal address is the element that makes the owner findable by someone who has no account and no relationship with them. It is also the element businesses drop first when they build a campaign landing page separately from the main site, because the page is designed to convert rather than to inform.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "landing page contact block",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Add the business postal address to the footer of every page a message can reach, alongside a phone number or email. Done when the landing page shows a street address, a city, a state and a postal code.",
      "notes": "Distinct from WEB-114, which requires the landing page to name the owner: this rule is about the address specifically, which is the part most often missing from an otherwise identified page.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-116",
      "slug": "web-116",
      "title": "A landing page that collects personal information must publish a privacy policy",
      "statement": "Any landing site collecting personal information must carry a conspicuously accessible privacy policy.",
      "rationale": "The page where a consumer types their phone number is the page where they are entitled to know what happens to it, and a policy two clicks away on a different domain does not reach them at the moment it matters. Campaign landing pages are built to convert and routinely ship without the footer the main site has, so this fails on exactly the page the messages point at.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "landing page policy link",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "7100",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Link the privacy policy from the landing page itself, next to the form rather than only in a footer nobody scrolls to. Done when the policy is reachable in one click from the page that collects the number.",
      "notes": "WEB-045 asks the same question of the brand home page. Both are kept because a brand routinely satisfies one and not the other: the main site has a footer and the campaign landing page does not.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-119",
      "slug": "web-119",
      "title": "The whole site must be screened for prohibited content, not just the landing page",
      "statement": "Prohibited-content screening must cover the entire website, not only the page the campaign points at.",
      "rationale": "Vetting crawls the brand's whole site, so a compliant campaign landing page does not protect a business whose shop sells vape hardware two clicks away. This is a common and expensive surprise: the campaign content is clean and the registration still fails on the brand.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website (all pages)",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio",
        "AWS",
        "T-Mobile"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Audit the whole site against the prohibited-content list before registering. Where the business genuinely sells a restricted category alongside a compliant one, expect the brand to be judged on the restricted category.",
      "notes": "Requires multi-page crawling. Today the product fetches a single page, so this rule is a known coverage gap rather than an implemented check.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-122",
      "slug": "web-122",
      "title": "No adult content on the site or any page it links to",
      "statement": "The brand website must not host or promote adult, pornographic, escort or nudity content.",
      "rationale": "Adult content is the category carriers refuse outright rather than gate, because the recipient of an unwanted message has no way to un-see it and the complaint goes straight to the carrier. The site is screened as well as the messages because the brand is what carries the traffic: a compliant booking campaign registered by an adult venue is refused on the venue, not on the campaign.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "AT&T",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30953",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This category cannot be registered for 10DLC in the US. There is no age gate or paywall that makes it registrable, and no re-framing of the campaign that separates it from the brand.",
      "notes": "The message-side twin is MSG-SHAFT-SEX. Both are kept because they fail independently: this one fires on a clean campaign whose brand website carries the content.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-123",
      "slug": "web-123",
      "title": "No hate-group brand",
      "statement": "The brand website must not host or promote the identity, materials or fundraising of a hate group.",
      "rationale": "CTIA names hate groups as a category carriers will not carry, and the assessment is of the organisation rather than of any particular message — so nothing in the campaign can rescue it. Public listings are consulted alongside the site, which means a brand can be refused on an identification it has never seen and cannot argue with inside the registration.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This category cannot be registered. Where the brand is a research, journalism or advocacy organisation whose site quotes such material to counter it, say so in the campaign description and expect the registration to be reviewed by a person.",
      "notes": "Judged partly from external listings the product does not hold, so a PASS here is a PASS on the retrieved content only. Ranked alongside WEB-122 as a refusal rather than a finding.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-124",
      "slug": "web-124",
      "title": "No advertising that ridicules a protected group or trivialises an atrocity",
      "statement": "The brand website must not host or promote advertising that misrepresents or ridicules people by age, colour, national origin, race, religion, sex, sexual orientation or disability, or that trivialises a historic atrocity.",
      "rationale": "AT&T bars this in its own schedule, separately from hate-group content, because the harm is done by ordinary marketing rather than by an extremist organisation — an edgy campaign, a joke about a disability, a genocide used as a punchline in a sale. Businesses get here through a copywriter reaching for shock rather than through anything anyone would call a policy, which is why the fix is usually one page.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "HIGH",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove or rewrite the offending copy and imagery, then re-crawl before resubmitting. Done when nothing on the site sells a product by reference to who somebody is.",
      "example": "Replace a campaign page joking about an ethnicity with the same offer stated plainly: \"Acme Coffee — 20% off all espresso blends this weekend.\"",
      "notes": "Severity divergence inside the single source: AT&T Schedule 1 grades the discrimination clause HIGH and the atrocity-trivialisation clause MEDIUM. Strictest kept.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-125",
      "slug": "web-125",
      "title": "No graphic violence on the linked site",
      "statement": "The brand website must not host or promote excessive or graphic violence.",
      "rationale": "The rule exists for the person who follows a link from a text message and is shown something they did not ask to see, which is why it is assessed on the destination rather than on the message. It reaches ordinary businesses through imagery chosen for impact — a self-defence course, a game studio, a haunted attraction — where nothing about the business is objectionable and one page is.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "HIGH",
      "detectability": [
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Move graphic imagery behind the age gate rather than onto the public landing page, and choose a different image for the page the messages link to. Done when the destination of every campaign link is safe to open in public.",
      "example": "Campaign link points at https://acmecoffee.com/events — a text-and-photography event page — rather than at the artwork gallery.",
      "notes": "VISION because the finding is in the pictures: a site whose copy is unremarkable can fail on a single image, and a text-only crawl sees none of it.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-126",
      "slug": "web-126",
      "title": "No firearms, ammunition or explosives sales on the site",
      "statement": "The brand website must not host or promote the sale of firearms, ammunition, explosives or fireworks.",
      "rationale": "Two carriers treat firearms as age-gateable and two providers ban them outright, so the same site is registrable at one destination and refused at another — and a brand that builds around the permissive reading discovers the strict one at submission. The harm the strict reading protects against is a channel that reaches a phone without any of the checks a gun sale otherwise carries.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "Telnyx",
        "T-Mobile",
        "Verizon"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30957",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This category cannot be registered under the strict reading the registry applies. Where the business sells instruction, membership or non-firearm goods, separate that site from the sales catalog and register the one with no purchase path.",
      "notes": "Genuine divergence, not a strictness difference: T-Mobile §5.7 and Verizon treat firearms as age-gateable, Telnyx permits education-only content but bans sales, and Twilio and Bandwidth ban the category outright. The strict-superset model takes the outright ban as the default; a brand submitting only to a permissive destination should read this as a warning rather than a refusal.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-127",
      "slug": "web-127",
      "title": "No tobacco or vape product content on the site",
      "statement": "The brand website must not host or promote tobacco, vape, e-cigarette or nicotine product content.",
      "rationale": "Nicotine marketing reaching a phone is the specific harm the age-gating regime exists to prevent, and carriers found that gates on retail sites do not survive contact with a link in a text message. Operators in this trade are usually compliant with every rule their own regulator imposes, which is why the refusal lands as a surprise: the sector's own age verification is not the standard being applied.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Telnyx",
        "Bandwidth",
        "T-Mobile"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30958",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This category cannot be registered under the strict reading. Where a general retailer carries a small nicotine range, expect the brand to be judged on that range rather than on the rest of the catalog.",
      "notes": "Age-gateable under T-Mobile §5.7 and banned outright by Twilio, so this is a real divergence between destinations. Strictest kept, per the superset model. The message-side twin is MSG-SHAFT-TOBACCO.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-128",
      "slug": "web-128",
      "title": "No cannabis, CBD, hemp or kratom content anywhere on the site",
      "statement": "The brand website must not host or promote cannabis, CBD, hemp, kratom, dispensary or drug-paraphernalia content.",
      "rationale": "The industry applies federal law where federal and state law diverge, so a fully licensed dispensary in a legal state is refused exactly like an unlicensed one — and this is the rule that surprises operators more than any other, because everything about their business is lawful where they stand. The screen covers the whole site rather than the landing page, so a wellness retailer with one CBD line fails on that line.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "Telnyx",
        "AWS",
        "TCR"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30940",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "701",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This category cannot be registered for 10DLC in the US regardless of state licensing. Removing the product pages does not help while the business is the dispensary; the refusal is on the brand.",
      "notes": "AWS marks the category non-remediable, and Telnyx extends the prohibition to shipping services for these products and cites a T-Mobile fine. State legality is irrelevant to the outcome and saying so early saves the appeal.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-129",
      "slug": "web-129",
      "title": "No gambling content on the site",
      "statement": "The brand website must not host or promote casino, sportsbook, lottery or online gambling content.",
      "rationale": "Gambling is banned by some providers and routed to a special use case by others, so the safe default is refusal — and the split is invisible from outside, which is how a licensed operator ends up registering into a destination that will not take it. The consumer harm behind the strict reading is the same one age gates exist for, arriving on a device with no gate in front of it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Telnyx"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30944",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Confirm before registering whether your destination offers a gambling or sweepstakes use case; several refuse the category outright. Where one exists, register under it rather than under marketing.",
      "notes": "Telnyx allows a licensed-and-age-gated carve-out and AWS marks the category non-remediable. Strictest kept. The message-side twin is MSG-GAMBLING.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-130",
      "slug": "web-130",
      "title": "No high-risk financial services on the site",
      "statement": "The brand website must not host or promote payday and short-term high-interest lending, third-party loan solicitation, debt collection, credit repair, debt forgiveness, crypto or retail investment content.",
      "rationale": "This is the category most associated with consumer-harm complaints on SMS, and AWS records it as not eligible for resubmission at all — a rejection here ends the registration rather than opening a fix loop. The line that decides an outcome is first-party versus third-party: a bank servicing its own loans is a different business from a site that solicits borrowers for other lenders, and the site is where the difference shows.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Telnyx",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30942",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "709",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This business model cannot be registered. A regulated lender servicing its own customers is a different category — if that is you, remove the acquisition-style offers from the site and declare the direct-lending attribute instead of concealing the lending.",
      "notes": "AWS states the category is \"not eligible for resubmission under A2P 10DLC\" — permanently non-remediable, unlike most BLOCKING findings. The first-party carve-out is what keeps a licensed lender registrable and must not be dropped: it is the same carve-out MSG-HIGH-RISK-FINANCIAL carries on the message side.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-131",
      "slug": "web-131",
      "title": "No lead-generation, affiliate-marketing or SEO-services content on the site",
      "statement": "The brand website must not host or promote lead generation, affiliate marketing or SEO and marketing-agency services.",
      "rationale": "Selling consumer contact details onward is the activity the whole consent framework exists to stop, so a site that advertises it describes a business model carriers refuse rather than a page they want edited. The trap for honest businesses is the word rather than the trade: an ordinary shop with a partner or referral programme page reads, to a screen looking for exactly this, like a lead broker.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "Bandwidth",
        "Telnyx",
        "Twilio"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30951",
          "remediable": false,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth/DCA",
          "code": "708",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove lead-generation, affiliate and SEO-services copy from the brand website and re-crawl before resubmitting; Telnyx states the remediation in exactly those terms. Done when nothing on the site offers to pass a visitor's details to anybody else. Where that copy describes what the business actually does, the registration cannot be saved by editing.",
      "example": "Replace \"Join our affiliate network and monetise your traffic\" with \"Refer a friend and you both get $10 credit — we never share your friend's details.\"",
      "pitfalls": [
        "AWS and Twilio both mark their codes non-remediable, so a rejection under this heading ends that campaign even where the site edit would have been enough. Fix the site before submitting, not after."
      ],
      "notes": "failureClass is TERMINAL_WEBSITE because the common case is removable copy and Telnyx publishes exactly that remediation, while the provider codes are marked non-remediable. The two answer different questions — ours is what to do before submitting, theirs is what happens after a rejection.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-133",
      "slug": "web-133",
      "title": "Sweepstakes content on the site must be registered as sweepstakes",
      "statement": "The brand website must not host or promote sweepstakes, prize-draw or contest-entry content that the registration does not declare.",
      "rationale": "Prize promotions carry their own use case and their own disclosure obligations, and a brand running one under a marketing registration has skipped both. AWS rejects on the brand rather than the campaign, so a business whose messages never mention the giveaway is still refused for the page that hosts it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Register under the sweepstakes use case where the promotion is live, and publish the official rules alongside the entry page. Where the promotion is over, take the page down or mark it closed before resubmitting.",
      "example": "Use case: SWEEPSTAKE, with official rules published at https://acmecoffee.com/rules and linked from the entry page.",
      "notes": "Related to but distinct from MSG-206, which reads the campaign copy. This one fires on site evidence alone, which is the case a campaign-only screen cannot see.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-134",
      "slug": "web-134",
      "title": "No multilevel-marketing content on the site",
      "statement": "The brand website must not host or promote multilevel-marketing or distributor-recruitment content.",
      "rationale": "MLM recruitment over SMS produces complaint volumes carriers treat as a category problem rather than a sender problem, so the screen is applied to the business rather than to the messages. Individual distributors are caught by it while believing they are registering an ordinary small retail business, because from the inside that is what it is.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Telnyx"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This business model cannot be registered. An independent distributor cannot separate themselves from it by registering the personal brand instead — the site is what is screened.",
      "notes": "Single-source in the catalog (Telnyx AUP) but authored universally: the message-side twin MSG-LEAD-GEN carries the same prohibition on recruitment content across several providers, so narrowing this to one provider would drop a real requirement.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-135",
      "slug": "web-135",
      "title": "No site impersonating another service to collect credentials",
      "statement": "The brand website must not host or promote impersonation of a legitimate service in order to capture credentials, identity numbers or other sensitive data.",
      "rationale": "This is the phishing pattern the whole landing-page review exists to catch: a page that looks like a bank, a carrier or a delivery service, reached from a text message, collecting whatever the real one would ask for. It is judged from design and form fields together, because the copy alone is by construction indistinguishable from the genuine article.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Twilio",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30960",
          "remediable": false,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered. Where the brand is an authorised partner using another company's marks legitimately, state the relationship in visible page content and expect the registration to be reviewed by a person.",
      "notes": "CTIA A65-03 extends the same prohibition to brand impersonation on any message landing page or download, and A65-04 to promotion of illegal activity. Both are discharged here.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-136",
      "slug": "web-136",
      "title": "No malware or insecure downloads served from the site",
      "statement": "The brand website must not host or promote malware, or application downloads served from non-secure locations.",
      "rationale": "A link in a text message that ends in a drive-by download is the highest-severity outcome in the whole framework, so the destination is screened for it directly. The common cause is not malice: an unmaintained plugin on a small business site gets compromised, the owner never sees the injected script, and the first they hear of it is a registration refusal that says nothing about their site being hacked.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA",
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Scan and clean the site, update the platform and plugins, and serve every download over https from your own domain. Done when a safe-browsing check on the domain comes back clear and no download link points at a plain-HTTP host.",
      "example": "App links point at https://apps.apple.com/... and https://play.google.com/... rather than at a self-hosted APK.",
      "pitfalls": [
        "Cleaning the site does not clear the blocklist entry. Request a review with the safe-browsing service as well, or the domain reputation rules keep failing after the malware is gone."
      ],
      "notes": "The landing-page malware screen is routed to WEBSITE by the catalog's layer-ownership rule, so it carries no MESSAGE_CONTENT alias: the message-side row was folded into this obligation rather than kept as a separate one.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-137",
      "slug": "web-137",
      "title": "No deceptive marketing claims on the site",
      "statement": "The brand website must not host or promote unsubstantiated health or product claims, fabricated urgency, unverifiable guarantees, unauthenticatable testimonials or false endorsements.",
      "rationale": "CTIA folds the FTC truth-in-advertising rules into the messaging framework, so the site is read against them and a claim that would draw an FTC letter draws a carrier rejection first. Almost every business trips this through ordinary conversion copywriting — a countdown that resets, a \"doctor recommended\" with no doctor, a testimonial with initials instead of a name — none of which was written to deceive anyone.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "AT&T",
        "CTIA",
        "FTC"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30962",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Qualify or remove each claim: name the source for a health or performance claim, make the deadline on any urgency real, and attribute testimonials to identifiable people. Done when every claim on the page could be defended with something you hold.",
      "example": "\"In a 2025 survey of 400 Acme Coffee subscribers, 82% said they brewed more often at home\" — rather than \"everyone brews better with Acme\".",
      "notes": "Distinct from WEB-024, which asks whether the site is real at all. A completely genuine business fails this rule on its marketing copy.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-139",
      "slug": "web-139",
      "title": "No counterfeit goods or falsified documents sold on the site",
      "statement": "The brand website must not host or promote counterfeit, replica or imitation designer goods, fake identification or falsified documentation.",
      "rationale": "AT&T bars the category outright because the messages promoting it are, by construction, promoting a crime — and fake identification carries a harm beyond the trade itself. The replica trade advertises openly enough that this is usually read straight off a product page rather than inferred.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This cannot be registered. A resale business that deals in authenticated genuine goods should say so on the product pages and in the campaign description, since the distinction is exactly what the screen is looking for.",
      "notes": "Severity divergence in the source: AT&T Schedule 1 grades counterfeit goods HIGH and fake identification BLOCKING. Strictest kept, and both limbs are one screen because they sit on the same catalog page in practice.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-140",
      "slug": "web-140",
      "title": "No law-evasion or questionable-legality products in the catalog",
      "statement": "The brand website must not host or promote law-evasion and protection-bypass products, or products of questionable legality.",
      "rationale": "AT&T names a specific old list — radar detectors, descramblers, copyright-bypass tools, miracle cures, witchcraft and good-luck merchandise — and it survives because those categories still attract the complaint patterns that put them there. A general retailer meets it through a single novelty line nobody thought about, which is why the fix is almost always the catalog rather than the business.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AT&T"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Remove the named product lines from the catalog, or drop the effectiveness claim where the item is a novelty. Done when nothing in the catalog is sold as a way around a law or as a remedy it cannot deliver.",
      "example": "List a \"lucky bean charm — novelty gift, no claims made\" rather than \"brings prosperity within 7 days\".",
      "notes": "Severity divergence inside the source: the questionable-legality clause is graded HIGH and the descrambler and radar-detector clauses MEDIUM. Graded MEDIUM overall because the enforcement in practice attaches to the product line rather than the brand, and the fix is a catalog edit.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-141",
      "slug": "web-141",
      "title": "No infringing content on the site",
      "statement": "The brand website must not host or promote content that infringes another party's intellectual property.",
      "rationale": "CTIA added this in SCMH v1.9, and it reaches messaging because a link in a text message is a distribution channel like any other — the rights holder's complaint arrives at the carrier. Small businesses trip it with stock imagery they did not license and fonts or music lifted from elsewhere, none of which they would describe as infringement.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Replace unlicensed media with material you hold a licence for, and remove third-party marks you are not authorised to use. Done when every image, font and logo on the site traces to a licence or to your own work.",
      "example": "Swap an unlicensed press photo on the home page for your own photography of the Mission St roastery.",
      "effectiveFrom": "2025-01-01",
      "notes": "New in CTIA SCMH v1.9. `effectiveFrom` is set to the start of 2025 because the catalog records the version rather than a publication date, and dating it earlier would apply a requirement before it existed.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-142",
      "slug": "web-142",
      "title": "A storefront catalog must be screened for SHAFT merchandise before submitting through a strict platform",
      "statement": "The brand website must not host or promote SHAFT merchandise in the storefront catalog, where the destination platform refuses such merchants outright.",
      "rationale": "Some ISV platforms refuse SHAFT merchants entirely rather than age-gating them, which is stricter than the carrier baseline and invisible until a submission is refused. A homeware store with a single hip-flask-and-whiskey gift set is a SHAFT merchant to that screen, and the seller has no reason to think of themselves as one.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "TCR"
      ],
      "applicability": {
        "verticals": [
          "RETAIL"
        ]
      },
      "applicabilityText": "Applies when the vertical is RETAIL.",
      "universal": false,
      "remediation": "Search your own catalog for alcohol, tobacco, firearms and adult items — including bundles — before submitting, and confirm with your platform whether it refuses such merchants outright or age-gates them. Done when you can state which of the two applies to your account.",
      "example": "Catalog audit: 0 results for alcohol, tobacco, firearm and adult categories across all products and gift sets.",
      "notes": "The catalog conditions this on `provider=Postscript`, which is not in the Provider enum and would in any case be the wrong shape: a provider tag narrows when a rule fires, and this platform-strictness is a warning every retail merchant benefits from. Tagged to the retail vertical instead, with the platform condition carried in the criteria.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-143",
      "slug": "web-143",
      "title": "Affiliate marketing plus lending on the site is a harder rejection than either alone",
      "statement": "The brand website must not host or promote affiliate or lead-generation activity in the campaign combined with high-risk lending on the website.",
      "rationale": "Bandwidth escalates the two signals together rather than scoring them independently, because the combination is the shape of a payday-loan lead broker — the highest-complaint pattern on the channel. It matters to an honest business because remediating one limb leaves the escalation in place: taking the affiliate page down while the lending stays up does not return the brand to where it started.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "crawled website content",
      "severity": "BLOCKING",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "HARD_STOP",
      "authorities": [
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "3101",
          "remediable": false
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "This combination cannot be registered. Removing one limb does not restore the other to a clean score, so a business that genuinely does both needs to separate them into different entities before either can be registered.",
      "notes": "The applicability model has no \"both limbs present\" predicate, so the condition lives in the criteria: the judge passes immediately unless it finds affiliate or lead-sharing activity in the campaign AND lending content on the site. Recorded here rather than left implicit, so the rule does not read as universal.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-145",
      "slug": "web-145",
      "title": "A business social profile can stand in where no website exists",
      "statement": "Where the brand genuinely has no website, an established, active, public business profile is accepted as the online presence.",
      "rationale": "Plenty of real small businesses trade entirely through a Google Business Profile, a Facebook page or an Etsy shop, and refusing them outright would fail the registrations the framework least wants to lose. What providers actually reject is a private or dormant profile — the evidential work is being done by the profile being open and alive, not by it being a website.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "submitted social profile URL",
      "severity": "MEDIUM",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "Twilio",
        "Bandwidth",
        "Telnyx",
        "AWS",
        "Sinch"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30490",
          "remediable": true,
          "generation": "gen2"
        },
        {
          "provider": "Bandwidth",
          "code": "TFV 1202",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Submit a public profile on a platform the providers name — Facebook, Instagram, LinkedIn, Yelp, Google Business Profile or Etsy — that shows recent activity and is visible without logging in. Done when the profile opens in a private browser window and its most recent post is inside the last few months.",
      "notes": "Absorbs BRD-137. Bandwidth TFV 1202 rejects a private or inactive account, which is the failure mode this rule is really about — the substitution itself is permitted.",
      "catalogIds": [
        "BRD-137"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-146",
      "slug": "web-146",
      "title": "A substituted profile must be a business page, not a personal one",
      "statement": "A social profile standing in for a website must be a business page carrying the business name, a description of what it does, and contact information.",
      "rationale": "A personal timeline proves a person exists, which is not the question — the registration is asserting that a business exists and does what the campaign claims. The distinction matters most for the sole proprietors this route was built for, who often run the business from the account they already had, and who can fix it by converting the page rather than building a site.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "substituted social profile type and content",
      "severity": "HIGH",
      "detectability": [
        "CRAWL",
        "VISION"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "AWS",
        "Twilio",
        "Zoom"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30490",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Convert the profile to a business or page account and fill in the name, the category, a description of the products or services, and a contact method. Done when the profile shows the registered business name and a way to reach it without sending a direct message.",
      "notes": "Absorbs BRD-138. Readable either from the crawled profile text or from a screenshot, which is why it is dual CRAWL + VISION rather than vision-only.",
      "catalogIds": [
        "BRD-138"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-147",
      "slug": "web-147",
      "title": "A social profile must not stand in for a website the brand has",
      "statement": "A social-media profile is accepted as the online presence only where the brand genuinely has no website of its own.",
      "rationale": "The substitute exists for businesses that never built a site, and it costs them extra scrutiny. Using it while a real site exists throws away the strongest evidence the registration has, and a reviewer who finds the website independently reads the substitution as an attempt to keep them off it.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website vs brand.social_profile_urls",
      "severity": "HIGH",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "1100",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the business website in brand.website and leave the social profiles as supporting links. Done when brand.website holds the domain the business trades on rather than a profile page.",
      "example": "website: https://acmecoffee.com — with facebook.com/acmecoffee listed as a social profile, not as the website.",
      "notes": "Single secondary source in the catalog (alive5) with no carrier code behind it, so it is graded HIGH rather than BLOCKING and worded as a misuse of the substitute rather than a prohibition.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-148",
      "slug": "web-148",
      "title": "The profile substitute is for small businesses without an established site",
      "statement": "The social-profile route is available to small businesses and sole proprietors that have no established website, not to larger businesses choosing not to submit one.",
      "rationale": "Two providers restrict the substitution by business size while the others do not, so the same profile is accepted at one destination and rejected at another. A business large enough to be expected to have a site, submitting a profile instead, reads as evasive rather than small — and that judgement is made by a person, from facts about the business we do not hold.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "entity size vs the substitute path",
      "severity": "MEDIUM",
      "detectability": [
        "HUMAN"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "Sinch",
        "SignalWire"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Where the business has any web presence of its own, register it and keep the profile as a supporting link. Where it genuinely has none, expect the substitute to be questioned and be ready to say why no site exists.",
      "notes": "Nothing on the form carries employee count, revenue or trading history, so we cannot decide whether a brand is small enough for this route. The user has to make the call themselves: if the business would look odd to a stranger without a website, build one before registering. The catalog records this as a genuine divergence — Sinch and SignalWire restrict the substitution by size, Twilio and Bandwidth do not.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Would this business look odd to a stranger for having no website of its own?",
        "howToCheck": [
          "Judge it as a reviewer would, from size and trading history: the social-profile substitute is for small businesses and sole proprietors with no established site.",
          "Where the business has any web presence of its own, register that and keep the profile as a supporting link.",
          "Where it genuinely has none, be ready to say why."
        ],
        "failureLooksLike": "A substantial business submits a Facebook page instead of a site. Sinch and SignalWire restrict the substitution by size and Twilio and Bandwidth do not, so the same profile is accepted at one destination and read as evasive at another."
      }
    },
    {
      "id": "WEB-152",
      "slug": "web-152",
      "title": "A sole proprietor site must not present as a registered company",
      "statement": "Where the brand is registered as a sole proprietor, the website must not hold itself out as an LLC or incorporated entity.",
      "rationale": "A site displaying \"Acme Coffee LLC\" behind a sole proprietor registration tells the reviewer the tier is wrong — the business evidently is a registered entity and should have registered as one. The mismatch is caught on the website even when the brand form looks internally consistent.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand website + brand.entity_type",
      "severity": "HIGH",
      "detectability": [
        "CRAWL"
      ],
      "failureClass": "TERMINAL_WEBSITE",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Either register the brand as the company the website advertises, using its EIN, or remove the corporate designation from the site if it is not accurate.",
      "phase": "approval",
      "automated": true
    },
    {
      "id": "WEB-153",
      "slug": "web-153",
      "title": "A charity campaign must supply both its own site and an accreditation listing",
      "statement": "A Charity campaign must supply the charity's own website URL and a listing URL at an accreditation organisation, and both must resolve.",
      "rationale": "Donation messaging is the most impersonated category there is, so the carriers demand a second, independent source that the charity exists — a listing nobody can create for themselves. A real charity that has simply never registered with an accreditation body is stopped here, and finding that out at submission rather than after a rejection is worth weeks.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "charity website URL + accreditation listing URL",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "T-Mobile",
        "Bandwidth",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "T-Mobile",
          "code": "9002",
          "remediable": true
        },
        {
          "provider": "Bandwidth",
          "code": "TFV 1110",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Supply the charity's own website alongside a listing at an accreditation organisation — a GuideStar/Candid profile, a Charity Navigator entry, or the state charity register — and open both links yourself before submitting.",
      "notes": "We cannot verify an accreditation listing, and we do not hold the listing URL as a field. The user has to obtain it, confirm it resolves publicly, and supply it with the registration alongside the organisation name and EIN.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Do you have both URLs — the charity's own site and its accreditation listing — and do both open in a private window?",
        "howToCheck": [
          "Obtain the listing URL: a GuideStar/Candid profile, a Charity Navigator entry, or the state charity register.",
          "Open both links yourself, logged out, before submitting.",
          "Supply them with the organisation name and EIN."
        ],
        "failureLooksLike": "A real charity that never registered with an accreditation body is stopped here. Donation messaging is the most impersonated category there is, so a second, independent source that the charity exists is not negotiable."
      }
    },
    {
      "id": "WEB-154",
      "slug": "web-154",
      "title": "A political campaign must supply a resolving organisation website",
      "statement": "A Political campaign must supply the candidate's or organisation's website URL alongside the FEC Committee ID, and that URL must resolve.",
      "rationale": "The Committee ID proves a filing exists; the website is what ties the filing to the messages a voter is about to receive. Campaign sites are stood up and taken down on a schedule nobody else keeps, so a URL that worked when the registration was drafted is a common and entirely innocent way to fail a political vet.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "political organisation website URL",
      "severity": "BLOCKING",
      "detectability": [
        "EXTERNAL_DATA"
      ],
      "failureClass": "TERMINAL_EVIDENCE",
      "authorities": [
        "T-Mobile",
        "Bandwidth",
        "Infobip"
      ],
      "codes": [
        {
          "provider": "T-Mobile",
          "code": "9001",
          "remediable": true
        }
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Supply the campaign or organisation website beside the FEC Committee ID, and confirm it loads publicly on the day you submit and stays up through the review window.",
      "notes": "We hold neither the Committee ID nor a separate political-website field, and we cannot check a filing against the FEC. The user has to pair the two themselves and keep the site live until the campaign is approved.",
      "phase": "approval",
      "automated": false,
      "attestation": {
        "question": "Does the campaign or organisation website load publicly today, and will it stay up through the review window?",
        "howToCheck": [
          "Open it in a private window and confirm it loads for someone who is not logged in.",
          "Pair it with the FEC Committee ID in the submission.",
          "Keep it live until the campaign is approved — campaign sites are stood up and taken down on a schedule nobody else keeps."
        ],
        "failureLooksLike": "A URL that worked when the registration was drafted is down when the vet runs. The Committee ID proves a filing exists; the site is what ties the filing to the messages a voter is about to receive."
      }
    },
    {
      "id": "WEB-157",
      "slug": "web-157",
      "title": "A changed domain must be updated on the brand record",
      "statement": "When the business moves to a new domain, the registered brand website must be updated to match.",
      "rationale": "The brand record is re-checked long after approval, so a stale website field turns into a dead link on somebody else's schedule — and the consequence is not a request to update it but delivery problems and a re-vetting flag. Nobody thinks of the 10DLC record when a domain changes, because it is not part of any migration checklist.",
      "layer": "WEBSITE",
      "layerSlug": "website",
      "object": "brand.website currency",
      "severity": "MEDIUM",
      "detectability": [
        "UNDETECTABLE_PRE_SUBMISSION"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "EZ Texting"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Update brand.website on the brand record as part of any domain migration, and keep the old domain redirecting until the change has been through vetting. Done when the registered URL is the one customers are being sent to.",
      "notes": "Post-approval hygiene, invisible at submission. The user has to put the brand record on their own domain-migration checklist alongside DNS, email and analytics — nothing prompts them, and the failure surfaces as a delivery problem rather than as a registration one.",
      "phase": "post",
      "automated": false,
      "attestation": {
        "question": "Is the 10DLC brand record on your domain-migration checklist, alongside DNS, email and analytics?",
        "howToCheck": [
          "Add brand.website to the checklist now — nothing will prompt you at migration time.",
          "Update the brand record as part of the move, and keep the old domain redirecting until the change has been through vetting."
        ],
        "failureLooksLike": "The brand record is re-checked months later against a dead link. The consequence is a re-vetting flag and delivery problems, not a request to update the field."
      }
    },
    {
      "id": "XBC-001",
      "slug": "xbc-001",
      "title": "Sole proprietor brands may only run the sole proprietor use case",
      "statement": "A SOLE_PROPRIETOR brand must register its campaign under the SOLE_PROPRIETOR use case; standard campaign types are unavailable to that tier.",
      "rationale": "The sole proprietor tier exists as a low-volume path for individuals with no EIN, and it comes with its own throughput caps and a single-number limit. Standard use cases assume a vetted registered entity behind them, so the platform refuses the combination rather than granting an individual standard-tier treatment.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "dependsOn": [
        "BRD-065"
      ],
      "remediation": "Select the Sole Proprietor use case for this campaign. If you need a standard use case such as Marketing, obtain an EIN and re-register the brand as a standard entity first — the tier, not the campaign, is what limits you.",
      "example": "Use case: SOLE_PROPRIETOR",
      "notes": "Catalog BRD-089. The most common failure after a sole proprietor brand is itself accepted.",
      "catalogIds": [
        "BRD-089"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-002",
      "slug": "xbc-002",
      "title": "Charity use case requires a verified 501(c)(3) non-profit brand",
      "statement": "The CHARITY use case is restricted to a NON_PROFIT brand holding verified 501(c)(3) tax-exempt status.",
      "rationale": "Charitable messaging gets favourable treatment — better throughput and more tolerance for solicitation content — so eligibility is verified against the brand record rather than taken on trust. 501(c)(4), (5) and (6) organisations are tax-exempt but are NOT charities for this purpose, which is where most rejections come from.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + brand.tax_exempt_status + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "Infobip"
      ],
      "applicability": {
        "useCases": [
          "CHARITY"
        ]
      },
      "applicabilityText": "Applies when the use case is CHARITY.",
      "universal": false,
      "remediation": "Register the brand as NON_PROFIT with its 501(c)(3) EIN and declare the tax-exempt status on the brand record. The EIN must appear in the IRS Tax-Exempt Organization Search. If the organisation is a 501(c)(4) or (6), choose a different use case.",
      "example": "Entity type: NON_PROFIT · Tax exempt status: 501(c)(3) · Use case: CHARITY",
      "catalogIds": [
        "BRD-054",
        "BRD-056"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-003",
      "slug": "xbc-003",
      "title": "Political use case requires a non-profit brand with political vetting",
      "statement": "The POLITICAL use case must sit on a NON_PROFIT brand carrying external political vetting recognised by both AT&T and T-Mobile.",
      "rationale": "Political messaging is the most scrutinised category on the network, and both major carriers require independent verification that the sender is who they claim to be before granting throughput. Vetting is imported into TCR from an external provider, so it cannot be satisfied by anything you type into the form.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "AT&T",
        "T-Mobile"
      ],
      "applicability": {
        "useCases": [
          "POLITICAL"
        ]
      },
      "applicabilityText": "Applies when the use case is POLITICAL.",
      "universal": false,
      "remediation": "Register the brand as NON_PROFIT, then obtain political vetting (Campaign Verify or an equivalent both carriers accept) and import it into TCR before submitting the campaign. Do not order a political vet for a brand that already holds 501(c)(3/4/5/6) status — that combination is disallowed.",
      "notes": "Off-platform: only the entity type is checkable here. The vetting itself is bought from an external provider and imported into TCR, so the user must obtain it themselves and confirm the import landed before submitting — we can never return anything better than UNCLEAR on that half, and a PASS on entity type is not a prediction that the campaign will be approved.",
      "catalogIds": [
        "BRD-240",
        "OPS-166"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Is political vetting already imported and visible on the brand, rather than ordered, in progress, or held only as a certificate you have not uploaded?",
        "howToCheck": [
          "Open the brand at your provider and confirm a political vet (Campaign Verify, or an equivalent both AT&T and T-Mobile accept) is listed against it.",
          "Confirm the brand's entity type is NON_PROFIT, and that it does not also hold 501(c)(3/4/5/6) status — that combination is disallowed."
        ],
        "failureLooksLike": "The certificate is sitting in an inbox rather than in TCR. Nothing on the campaign form says the vetting is missing, and the campaign is refused for a brand that looks complete."
      }
    },
    {
      "id": "XBC-004",
      "slug": "xbc-004",
      "title": "Emergency and public-safety use cases exclude sole proprietors",
      "statement": "Public-safety and emergency use cases are not available to a SOLE_PROPRIETOR brand.",
      "rationale": "Emergency messaging bypasses filtering that protects consumers from everything else, so it is restricted to registered entities that can be held accountable for misuse. An individual with no EIN cannot be vetted to that standard.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + campaign.usecase",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Register the organisation as a standard entity with its EIN. If the messaging is genuinely operational rather than emergency, choose CUSTOMER_CARE or ACCOUNT_NOTIFICATION instead.",
      "example": "Entity type: PRIVATE_PROFIT or GOVERNMENT · Use case: EMERGENCY",
      "catalogIds": [
        "BRD-159"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-005",
      "slug": "xbc-005",
      "title": "Sole proprietor brand and campaign both require a reference ID",
      "statement": "referenceId is mandatory — not optional — on both the brand and the campaign for the sole proprietor tier.",
      "rationale": "The reference ID is how the sole proprietor tier ties a brand and campaign to the verified mobile number behind them, which is the only identity check that tier has. It is optional everywhere else, so integrations built against the standard flow omit it and fail here.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.reference_id + campaign.reference_id",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "remediation": "Populate referenceId on both the brand and the campaign with your own stable identifier for this customer.",
      "example": "referenceId: acme-sp-2026-0041",
      "notes": "Catalog BRD-077 / CMP-022.",
      "catalogIds": [
        "CMP-022",
        "BRD-077"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-006",
      "slug": "xbc-006",
      "title": "The brand must be verified before its campaign is submitted",
      "statement": "A campaign must not be submitted until the brand reaches an approved, verified identity status.",
      "rationale": "A campaign is judged against the brand as it stands the moment it arrives, so one submitted against an unverified brand is refused — and the refusal names the campaign. Teams then rewrite the description, the samples and the flow, none of which was the problem, and resubmit into the same wall. It happens because the brand call returns 200 with a pending status and looks like success.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.identity_status + campaign submission",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR",
        "AWS",
        "Twilio"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Hold the campaign until the brand identity status reads VERIFIED or VETTED_VERIFIED, and sequence the two submissions off the brand status event rather than off a timer. Done when the brand is in a final verified state at the moment the campaign is sent.",
      "notes": "The wait is at the registry and nothing here shortens it. What the user has to do is order the two submissions — brand first, campaign only once the status is final — because a campaign refused for this reason still consumes a submission and, at some providers, a fee. An UNVERIFIED status is not a brand defect on its own: it is the state a brand sits in while the check runs.",
      "catalogIds": [
        "OPS-312"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Does the brand read VERIFIED or VETTED_VERIFIED at the moment the campaign is submitted, rather than PENDING?",
        "howToCheck": [
          "Fetch the brand immediately before submitting the campaign and read the identity status — do not rely on the 200 the create call returned, which succeeds while the status is still pending.",
          "Sequence the campaign off the brand status webhook rather than off a timer, so the campaign leaves only once the status is final."
        ],
        "failureLooksLike": "The campaign is refused and the error names the campaign, so the description, samples and flow are rewritten and resubmitted into the same wall — while the brand was the thing that was not ready."
      }
    },
    {
      "id": "XBC-007",
      "slug": "xbc-007",
      "title": "A public company must finish two-factor identity before any campaign",
      "statement": "A PUBLIC_PROFIT brand must complete its Authentication+ two-factor identity step before a campaign is created against it.",
      "rationale": "Publicly traded brands carry an extra identity step that no other entity type has, and it is the one gate that produces no error while it is outstanding: the brand verifies, looks healthy, and every campaign against it is refused. The two-factor mail goes to the business contact address, so it is routinely missed by the person doing the registration, who is rarely the person on that mailbox.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + brand.vetting + campaign submission",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "TERMINAL_EXTERNAL",
      "authorities": [
        "TCR"
      ],
      "applicability": {
        "entityTypes": [
          "PUBLIC_PROFIT"
        ]
      },
      "applicabilityText": "Applies when the brand is a public company.",
      "universal": false,
      "dependsOn": [
        "XBC-006"
      ],
      "remediation": "Order the Authentication+ vet explicitly after the identity check, watch the business contact mailbox for the two-factor mail, and hold the campaign until the vet is recorded on the brand. Done when the brand shows an Authentication+ vet before the campaign is submitted.",
      "notes": "The vet is an action at the registry, not a field on the submission, and the PIN arrives by mail on the registry's schedule. What the user has to do is confirm who receives the business contact mail and that they know to expect it — BRD-225 covers ordering the vet, BRD-226 the window it must be completed in; this rule is the campaign-side consequence of either being outstanding.",
      "catalogIds": [
        "OPS-311"
      ],
      "phase": "approval",
      "automated": true,
      "attestation": {
        "question": "Do you know who receives mail at the business contact address, and that they are expecting the Authentication+ two-factor message?",
        "howToCheck": [
          "Name the person on the business contact mailbox out loud. It is rarely the person doing the registration.",
          "Tell them the two-factor mail is coming and what to do with it, then confirm the completed vet appears on the brand before submitting the campaign."
        ],
        "failureLooksLike": "The brand verifies and looks healthy, every campaign against it is refused, and the two-factor mail is unopened in a shared inbox nobody watches."
      }
    },
    {
      "id": "XBC-008",
      "slug": "xbc-008",
      "title": "A sole proprietor gets one campaign and one number",
      "statement": "A SOLE_PROPRIETOR brand may hold exactly one campaign, and that campaign exactly one telephone number.",
      "rationale": "The sole proprietor tier is a low-volume path for individuals with no EIN, and the single-campaign, single-number limit is what the carriers accept in exchange for the reduced vetting. A second campaign is refused rather than queued, so somebody who registers a marketing campaign beside their appointment reminders loses the fee and the time. Pooling is the same limit stated the other way: there is only ever one number to pool.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.entity_type + campaign count + campaign.number_pool",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "T-Mobile",
        "Twilio"
      ],
      "applicability": {
        "entityTypes": [
          "SOLE_PROPRIETOR"
        ]
      },
      "applicabilityText": "Applies when the brand is a sole proprietor.",
      "universal": false,
      "dependsOn": [
        "XBC-001"
      ],
      "remediation": "Fold everything the programme sends into the one sole-proprietor campaign, on one number, and turn number pooling off. If the business genuinely needs a second campaign or more throughput, obtain an EIN and re-register as a standard entity — the tier is the limit, not the campaign.",
      "example": "Sole proprietor: 1 campaign · 1 phone number · numberPool: false",
      "pitfalls": [
        "Registering the second campaign under a second sole-proprietor brand does not work either: the tier caps the mobile number behind the brands as well, so the same person cannot hold an unlimited number of them."
      ],
      "catalogIds": [
        "OPS-316"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-010",
      "slug": "xbc-010",
      "title": "Every sample message must identify the registered brand",
      "statement": "Each sample must contain the registered brand name or DBA, matching the brand record rather than a marketing alias.",
      "rationale": "A consumer receiving a text from an unfamiliar number decides in about a second whether it is legitimate or spam, and the sender name is the only signal they have. Carriers therefore require the brand to identify itself in the message body, and Bandwidth checks every sample rather than just one.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.display_name + campaign.sample[]",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "TCR",
        "CTIA"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "8101",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Start every sample with the registered brand name followed by a colon. Use the name on the brand record — a marketing alias the reviewer cannot match to the registration reads as a mismatch.",
      "example": "Acme Coffee: your order #4471 is ready for pickup until 6pm. Reply STOP to opt out, HELP for help.",
      "pitfalls": [
        "Twilio accepts the name in at least one sample; Bandwidth requires it in all of them. We apply the stricter rule."
      ],
      "catalogIds": [
        "MSG-036",
        "MSG-037",
        "MSG-039"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-011",
      "slug": "xbc-011",
      "title": "Opt-in description must carry the registered brand name",
      "statement": "The CTA / message-flow field must name the brand, matching the registered Brand or DBA.",
      "rationale": "The reviewer reads the message flow to confirm the consent a consumer gave was consent to hear from THIS brand. A flow describing an anonymous opt-in cannot establish that, and it is also the exact pattern a reseller registering someone else’s traffic would produce.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.display_name + campaign.message_flow",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Telnyx",
        "AWS"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Name the brand explicitly in the opt-in description, and quote the consent wording the consumer agrees to — that wording should name the brand too.",
      "example": "Customers opt in at acmecoffee.com/signup by ticking an unchecked box agreeing to receive recurring automated marketing texts from Acme Coffee.",
      "catalogIds": [
        "CMP-070"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-012",
      "slug": "xbc-012",
      "title": "HELP reply must identify the brand",
      "statement": "The HELP auto-reply must contain the registered brand name.",
      "rationale": "HELP exists so a recipient who does not recognise a number can find out who is texting them. A reply that omits the brand answers the wrong question and leaves reporting the message as spam as the consumer’s only remaining option.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.display_name + campaign.help_message",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "TCR",
        "Twilio",
        "CTIA"
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Put the brand name at the start of the HELP reply and add a support contact a person can actually reach. CTIA expects contact information beyond the bare name.",
      "example": "Acme Coffee: for help, email support@acmecoffee.com or call 415-555-0134. Msg & data rates may apply. Reply STOP to unsubscribe.",
      "catalogIds": [
        "CMP-191"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-013",
      "slug": "xbc-013",
      "title": "Brand, website and campaign content must describe the same business",
      "statement": "The registered brand name, the brand website, and what the campaign says it sends must be coherent.",
      "rationale": "Reviewers open the website and compare it against the campaign before approving anything. A mismatch — a pest-control site behind a campaign describing salon appointment reminders — is the classic signature of a reseller registering under a borrowed brand, so it is treated as an integrity failure rather than a copy problem.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.company_name + brand.website + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "AI_FORM"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Twilio",
        "TCR",
        "AWS",
        "Bandwidth"
      ],
      "codes": [
        {
          "provider": "Twilio",
          "code": "30907",
          "remediable": true,
          "generation": "gen2"
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Make the campaign description describe the business the website actually presents, under the name the brand is registered with. If the website belongs to a parent or agency rather than the sender, register the brand that owns the customer relationship instead.",
      "example": "Website acmecoffee.com sells coffee subscriptions; description: \"Acme Coffee sends order updates and promotional offers to subscription customers who opted in at checkout.\"",
      "catalogIds": [
        "MSG-145"
      ],
      "phase": "approval",
      "automated": true
    },
    {
      "id": "XBC-014",
      "slug": "xbc-014",
      "title": "The description must name the registered business",
      "statement": "The campaign description must name the registered legal name or the registered DBA.",
      "rationale": "The description is where a reviewer decides which business they are vetting, and one that never names the registrant leaves them matching a programme against a brand record on trust. It is the same defect as a wrong name and reads worse: a description naming nobody offers nothing to reconcile, so there is no way to establish that this campaign belongs to this brand at all.",
      "layer": "CROSS",
      "layerSlug": "cross",
      "object": "brand.company_name + campaign.description",
      "severity": "BLOCKING",
      "detectability": [
        "DETERMINISTIC"
      ],
      "failureClass": "RETRY_FIELD",
      "authorities": [
        "Bandwidth",
        "Infobip",
        "AWS",
        "Sinch",
        "Plivo"
      ],
      "codes": [
        {
          "provider": "Bandwidth",
          "code": "2006",
          "remediable": true
        },
        {
          "provider": "Infobip",
          "code": "603",
          "remediable": true
        }
      ],
      "applicabilityText": "Applies to every 10DLC registration.",
      "universal": true,
      "remediation": "Open the description with the registered name — the one on the brand record, not a product or campaign nickname — and keep it there. Done when the first sentence names the same business the brand record does.",
      "example": "Acme Coffee sends weekly promotional offers to customers who opted in at checkout — not \"We send weekly promotional offers to our customers\".",
      "pitfalls": [
        "A product or programme name is not the registered name: \"Acme Rewards\" does not match a brand registered as Acme Coffee Co, LLC unless Acme Rewards is the registered DBA."
      ],
      "notes": "The catalog grades this DETERMINISTIC + EXTERNAL_DATA because the strict form matches the description against the authoritative registry record. That lookup is not available here, so this rule settles the half that is: whether the description names the business the submission itself declares. A description agreeing with a brand record that is itself wrong is XBC-010 and CMP-037's territory.",
      "catalogIds": [
        "CMP-036"
      ],
      "phase": "approval",
      "automated": true
    }
  ]
}
