OdyDocs

Messaging (10DLC)

Register the workspace for US A2P 10DLC texting: check status, file the brand and campaign, refresh, and verify sole-proprietor PINs.

US carriers block application-to-person texts from local numbers unless the sender is registered with The Campaign Registry (TCR). The registration has two parts: a brand (who you are) and a campaign (what you text and how people opt in). This resource runs that registration for the workspace. It's the same pipeline as Settings → Texting in the app.

#The pipeline

Text
none → profile_ready → brand_pending ──→ brand_ok → campaign_pending → campaign_active ✓ canText
                             │                              │
                             ├→ brand_otp_pending (sole proprietors: submit the PIN)
                             └→ brand_failed                └→ campaign_failed

Carrier review usually takes minutes to a few days. Poll GET /messaging, or call POST /messaging/refresh to re-check right away.

#The messaging status object

  • step string — Where the registration is. See the pipeline above.
  • canText boolean — true once the campaign is carrier-approved. Until then, POST /messages returns 409.
  • messagingProfileId string | null — The carrier messaging profile.
  • brand object | null — status, identityStatus, failureReasons. For sole proprietors, also otpSentAt and otpMobileLast4.
  • campaign object | null — Campaign submission and carrier status.

#Retrieve registration status

GET/v1/api/messaging

Scope: numbers:manage

Returns { messaging }.

curl https://api.ody.co/v1/api/messaging \
  -H "Authorization: Bearer $ODY_API_KEY"
Response
{
  "messaging": {
    "step": "campaign_pending",
    "canText": false,
    "messagingProfileId": "40019…",
    "brand": { "status": "OK", "identityStatus": "VERIFIED", "failureReasons": [] },
    "campaign": { "status": "PENDING" }
  }
}

#Submit a registration

POST/v1/api/messaging/registration

Files the brand with TCR and stages the campaign. The campaign is created automatically once the brand is accepted.

Submitting is a purchase

A live filing ends a running free trial and charges the plan, plus the one-time $20 setup fee per local number, to the card on file immediately, before carriers accept anything. A resubmission never charges the same number again. The $4/mo messaging add-on starts when the campaign is approved.

  • A trialing workspace must send acknowledge_trial_end: true, confirming the customer was told. Otherwise you get 409.
  • With no payment method, a canceled subscription, or (while trialing) no submitted identity check, you get 409 "Add a payment method…".
  • If the card declines, nothing is filed.
  • The owner gets an emailed receipt with a masked copy of the application, and every filing is audit-logged with actor api.

With a test key, the filing always runs in mock mode: the full pipeline runs, but nothing is submitted to TCR, no fee is charged and the billing gate is skipped.

Scope: numbers:manage

Body

  • business object required — The legal identity. See below.
  • campaign object — Campaign settings. Sensible defaults are applied when you omit it.
  • acknowledge_trial_end boolean — Required (true) on a free trial.
  • mock boolean — Force mock mode on a live key (no fee, nothing filed).

business

  • entityType string — PRIVATE_PROFIT (default), PUBLIC_PROFIT, NON_PROFIT, GOVERNMENT or SOLE_PROPRIETOR.
  • displayName string required — The brand name customers see.
  • email string required
  • vertical string required — TCR vertical, for example PROFESSIONAL, RETAIL, REAL_ESTATE.
  • street, city, state, postalCode string required
  • companyName, ein string — Required unless SOLE_PROPRIETOR. EIN is 9 digits; the dash is optional.
  • firstName, lastName, mobilePhone string — Required for SOLE_PROPRIETOR. TCR texts a PIN to mobilePhone.
  • phone string — Support phone, in E.164 or 10-digit US format.
  • website string
  • country string — ISO 3166 alpha-2. Default US.

campaign

  • useCase string — Default MIXED.
  • description string — At least 40 characters. Built from the business name and use case when omitted.
  • samples array of strings — Up to 5 example messages.
  • optIn object — Preferred. { method: "web_form", url, location, formConfirmed: true, screenshotUrl? }. Ody writes a carrier-ready opt-in description from it. It overrides messageFlow.
  • messageFlow string — 40–2048 characters describing one concrete opt-in method. Carriers reject descriptions that list several methods or point at a form that doesn't exist.
  • optinMessage, helpMessage string
  • optinKeywords string — Default START.
  • embeddedLink, embeddedPhone boolean — Whether your messages contain links or phone numbers.

Returns 201 with { messaging, mock? }. A 409 means TCR or the carrier rejected the filing (the message says why) or a billing gate blocked it.

curl https://api.ody.co/v1/api/messaging/registration \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "business": {
      "displayName": "Acme Plumbing",
      "companyName": "Acme Plumbing LLC",
      "ein": "12-3456789",
      "email": "[email protected]",
      "vertical": "PROFESSIONAL",
      "street": "100 Congress Ave",
      "city": "Austin",
      "state": "TX",
      "postalCode": "78701",
      "website": "https://acmeplumbing.com"
    },
    "campaign": {
      "useCase": "CUSTOMER_CARE",
      "samples": ["Hi Ada, your plumber is on the way and will arrive by 2pm. Reply STOP to opt out."],
      "optIn": {
        "method": "web_form",
        "url": "https://acmeplumbing.com/contact",
        "location": "the quote form at the bottom of the contact page",
        "formConfirmed": true
      }
    },
    "acknowledge_trial_end": true
  }'

#Refresh status

POST/v1/api/messaging/refresh

Asks the carrier for the latest brand and campaign status and moves the pipeline forward (for example, it creates the campaign once the brand is accepted).

Scope: numbers:manage

Returns { messaging }.

curl -X POST https://api.ody.co/v1/api/messaging/refresh \
  -H "Authorization: Bearer $ODY_API_KEY"

#Verify a sole-proprietor PIN

POST/v1/api/messaging/otp/verify

Only used while step is brand_otp_pending. When a SOLE_PROPRIETOR brand is filed, TCR texts a PIN to business.mobilePhone. Submit it here. A correct PIN verifies the brand and creates the campaign immediately, so the response step is usually campaign_pending. PINs expire after 24 hours.

Scope: numbers:manage

Body

  • pin string required

Returns { messaging }. Returns 400 for a wrong or expired PIN, or 409 if the registration isn't waiting for one.

curl https://api.ody.co/v1/api/messaging/otp/verify \
  -H "Authorization: Bearer $ODY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pin":"482913"}'

#Resend a sole-proprietor PIN

POST/v1/api/messaging/otp/resend

Texts a fresh PIN to business.mobilePhone. You can request one per minute; asking again sooner returns 429.

Scope: numbers:manage

Returns { messaging }.

curl -X POST https://api.ody.co/v1/api/messaging/otp/resend \
  -H "Authorization: Bearer $ODY_API_KEY"
Last updated Wed Oct 14 2026 00:00:00 GMT+0000 (Coordinated Universal Time)Something wrong on this page? Tell us