Zernio
Zernio
PlatformsSMSCarrier RegistrationSendingSender IDs
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
SMS

Carrier Registration

Register a US number for SMS with 10DLC or toll-free verification through POST /v1/sms/registrations, reuse an approval on more numbers, and appeal a rejection.


When you finish this page a US number has a carrier registration in review, and you know how to add more numbers to it without paying again. You need an SMS-capable number and the legal details of the business sending the messages. US carriers require a number to be registered before it can send SMS; POST /v1/sms/registrations handles both paths:

  • 10DLC, for local (10-digit long code) numbers. Standard (company) or sole proprietor. Needs a brand and a campaign.
  • Toll-free verification, for toll-free numbers. Needs the tollFree details.

Approval is asynchronous: poll GET /v1/sms/registrations/{id} and do not assume delivery until status is approved. Registration is a US requirement only; a number in any other country sends as soon as SMS is enabled on it.

What registration costs

Registration bills 2 fees to your usage invoice: a $9 one-time brand fee, and a recurring campaign fee of $20 per month for standard 10DLC and toll-free, or $4 per month for sole proprietors. The fees show before you submit; per-destination message rates are in the SMS price list. Reusing an approval on more numbers costs nothing.

Enable SMS on the number first

Call POST /v1/phone-numbers/{id}/sms. Zernio checks the number's real carrier capability, and the response says which registration path applies.

curl -X POST "https://zernio.com/api/v1/phone-numbers/66d4e5f6a7b8c9d0e1f2a3b4/sms" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "enabled": true,
  "id": "66b2e19d8c3f5a7e9d0b1c2d",
  "phoneNumber": "+14155550100",
  "isActive": false,
  "country": "US",
  "smsCapable": true,
  "mmsCapable": true,
  "needsRegistration": true,
  "alreadyRegistered": false,
  "registrationStatus": null,
  "reusable": null
}

id is the account id the number sends from. isActive stays false on a US number until its registration is approved. enabled: false with smsCapable: false means the number cannot text; notReady: true means it is still provisioning at the carrier, so retry shortly. When reusable is set, skip to reuse.

Start a 10DLC registration

Call POST /v1/sms/registrations with registrationType, brand and campaign. registrationType is the only field the endpoint requires; brand and campaign are required for 10DLC, and phoneNumbers is optional. Send phoneNumbers anyway: omitting it enrols every active SMS-enabled US local number on the team that no other registration already covers, which is rarely what one campaign should cover.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: registration } = await zernio.sms.startSmsRegistration({
  body: {
    phoneNumbers: ['+14155550100'],
    registrationType: 'standard_10dlc',
    brand: {
      entityType: 'PRIVATE_PROFIT',
      displayName: 'Acme',
      companyName: 'Acme Inc.',
      ein: '12-3456789',
      phone: '+14155550199',
      country: 'US',
      vertical: 'TECHNOLOGY',
      website: 'https://acme.example.com',
      street: '123 Market St',
      city: 'San Francisco',
      state: 'CA',
      postalCode: '94105',
    },
    campaign: {
      usecase: 'CUSTOMER_CARE',
      description: 'Order updates and support replies for Acme customers.',
      messageFlow: 'Customers opt in at checkout on https://acme.example.com/checkout by ticking the SMS updates box.',
      sample1: 'Acme: your order #1234 has shipped. Reply STOP to opt out.',
      sample2: 'Acme: a support agent replied to your ticket. Reply HELP for help.',
      optinKeywords: 'START',
      optoutKeywords: 'STOP',
      helpKeywords: 'HELP',
    },
  }
});
const registrationId = registration.registrationId;

Response (200):

{
  "registrationId": "67a7b8c9d0e1f2a3b4c5d6e7",
  "status": "pending",
  "awaitingOtp": false
}

Carriers reject sparse filings, so several brand and campaign fields are hard requirements: the brand needs a website (sole proprietors may use a social profile URL instead), a full street address (street, city, state, postalCode) and, for company, non-profit and government brands, a business phone. The campaign needs a use case, a description, a messageFlow with a link to the page where people opt in, opt-in, opt-out and help keywords, and 2 distinct sample messages (sample1 and sample2, 20+ characters each). The auto-response messages (optinMessage, optoutMessage, helpMessage) are optional; Zernio generates a compliant template when they are omitted. POST /v1/sms/registrations/preflight runs the same checks plus a compliance review without creating anything.

Sole-proprietor registrations (sole_prop_10dlc) return awaitingOtp: true: a code is texted to the brand's mobilePhone, and the registration waits until you submit it with POST /v1/sms/registrations/{id}/verify-otp and otpPin. When that code expired or never arrived, POST /v1/sms/registrations/{id}/resend-otp sends a new one, at most once a minute. Standard company registrations skip this.

Start a toll-free verification

A toll-free number is verified by the toll-free aggregator instead of the 10DLC registry, so it takes no brand and no campaign. Call POST /v1/sms/registrations with registrationType: "toll_free" and a tollFree object.

curl -X POST "https://zernio.com/api/v1/sms/registrations" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "registrationType": "toll_free",
    "phoneNumbers": ["+18445550100"],
    "tollFree": {
      "businessName": "Acme Inc.",
      "corporateWebsite": "https://acme.example.com",
      "phoneNumbers": ["+18445550100"],
      "useCase": "Customer care",
      "useCaseSummary": "Order updates and support replies for Acme customers.",
      "productionMessageContent": "Acme: your order #1234 has shipped. Reply STOP to opt out.",
      "optInWorkflow": "Customers tick the SMS updates box at checkout on https://acme.example.com/checkout.",
      "optInWorkflowImageUrls": ["https://zernio.com/opt-in-proof/..."],
      "messageVolume": "10,000",
      "additionalInformation": "Transactional only, no promotions.",
      "businessAddr1": "123 Market St",
      "businessCity": "San Francisco",
      "businessState": "CA",
      "businessZip": "94105",
      "businessContactFirstName": "Dana",
      "businessContactLastName": "Reyes",
      "businessContactEmail": "dana@acme.example.com",
      "businessContactPhone": "+14155550199",
      "businessRegistrationNumber": "12-3456789",
      "businessRegistrationType": "EIN",
      "businessRegistrationCountry": "US"
    }
  }'

Response (200):

{
  "registrationId": "67a7b8c9d0e1f2a3b4c5d6e8",
  "status": "pending",
  "awaitingOtp": false
}

Every field in that body is required; businessAddr2 is the only optional one. Two of them decide the outcome. optInWorkflowImageUrls needs at least one screenshot of the opt-in form itself, so upload the image with POST /v1/sms/opt-in-proof (PNG, JPG, WebP, GIF or PDF, up to 4 MB) and pass the URL it returns. messageVolume is a monthly tier, one of 10, 100, 1,000, 10,000, 100,000, 250,000, 500,000, 750,000, 1,000,000, 5,000,000 or 10,000,000+.

Toll-free verification has no OTP step, so awaitingOtp is always false. Track it with the same GET /v1/sms/registrations/{id} as 10DLC. POST /v1/sms/registrations/{id}/appeal covers 10DLC campaigns only.

Reuse an approval (skip the fee)

Carriers charge a brand fee per registration. When you already hold an approved 10DLC campaign, attach the new number to it with POST /v1/phone-numbers/{id}/sms/reuse-registration instead of registering again. The number inherits the campaign's approval, with no new brand or campaign and no extra carrier fee. Enable SMS on the number first; its response carries the reusable registration.

curl -X POST "https://zernio.com/api/v1/phone-numbers/66d4e5f6a7b8c9d0e1f2a3b5/sms/reuse-registration" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "registrationId": "67a7b8c9d0e1f2a3b4c5d6e7",
  "status": "approved"
}

Hand the form off (white-label)

When your customer holds the legal business details, call POST /v1/sms/registrations/share with the number's numberId. It returns a single-use link (valid 7 days) where they fill in the registration form with no Zernio login; the registration is created under your team once they submit.

curl -X POST "https://zernio.com/api/v1/sms/registrations/share" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"numberId": "66d4e5f6a7b8c9d0e1f2a3b4"}'

Response (200):

{
  "url": "https://zernio.com/sms/register/share/...",
  "expiresAt": "2027-01-08T12:00:00Z"
}

Track and appeal

Call GET /v1/sms/registrations for every registration and GET /v1/sms/registrations/{id} for one.

curl "https://zernio.com/api/v1/sms/registrations/67a7b8c9d0e1f2a3b4c5d6e7" \
  -H "Authorization: Bearer $ZERNIO_API_KEY"

Response (200):

{
  "id": "67a7b8c9d0e1f2a3b4c5d6e7",
  "registrationType": "standard_10dlc",
  "status": "pending",
  "brandStatus": "VERIFIED",
  "campaignStatus": "PENDING",
  "declineReason": null,
  "phoneNumbers": ["+14155550100"],
  "awaitingOtp": false
}

Branch on status:

statusMeaningWhat to do
pendingSubmitted and under carrier review. On a sole-proprietor registration, awaitingOtp: true means it is paused on the OTP step.Poll, or wait. Messages do not deliver yet.
approvedThe registration is live; every number attached to it can send.Start sending, or attach more numbers.
rejectedThe carrier declined it; declineReason says why.Fix and appeal (below).

You may also see requested and changes_requested (pre-submission review states; answer a change request with POST /v1/sms/registrations/{id}/respond) and deactivated for terminated registrations, which the list hides unless you pass includeDeactivated=true. brandStatus, campaignStatus and trustScore are the raw carrier-registry fields, useful when referencing the filing with carrier support, but status is the field to branch on. Statuses are not one-way: an approved registration can drop back to pending when the carrier registry suspends the campaign, so treat status as the current state rather than a completed milestone.

Appeal a rejected campaign with POST /v1/sms/registrations/{id}/appeal: pass an appealReason that addresses declineReason directly, and fix the flagged content in the same call (messageFlow, sample1, sample2; the content on file is returned as campaignContent on a rejected registration). Carrier reviewers reject campaigns whose consent they cannot verify, so host a screenshot of your opt-in form with POST /v1/sms/opt-in-proof and put the returned URL inside messageFlow.

curl -X POST "https://zernio.com/api/v1/sms/registrations/67a7b8c9d0e1f2a3b4c5d6e7/appeal" \
  -H "Authorization: Bearer $ZERNIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "appealReason": "The opt-in checkbox is on the checkout page linked below; screenshot attached in the message flow.",
    "messageFlow": "Customers opt in at checkout on https://acme.example.com/checkout by ticking the SMS updates box. Screenshot: https://zernio.com/opt-in-proof/..."
  }'

Response (200):

{
  "status": "pending"
}

A brand-level rejection cannot be appealed: correct the brand details and re-verify instead.

If it fails

A 400 on POST /v1/sms/registrations names the missing field:

{
  "error": "campaign.sample2 is required",
  "type": "invalid_request_error",
  "code": "missing_required_field",
  "param": "campaign.sample2"
}

Add the field and resubmit; run POST /v1/sms/registrations/preflight first to catch every gap at once. A 422 means the carrier registry rejected a field; param names it when known. Every error uses the envelope in error handling.

Related

  • Sending: send once the number is approved.
  • Start a carrier registration: every brand, campaign and tollFree field.
  • Add a number to an existing registration and Appeal a rejected campaign: every field.
  • SMS rates: registration fees and per-segment prices.
Was this page helpful?

SMS

Send and receive SMS and MMS from your numbers with POST /v1/sms/messages, with US carrier registration handled through the API.

Sending

Send an SMS or MMS from an SMS-enabled number with POST /v1/sms/messages, schedule it, read opt-outs and look up a number's line type.

On this page

What registration costsEnable SMS on the number firstStart a 10DLC registrationStart a toll-free verificationReuse an approval (skip the fee)Hand the form off (white-label)Track and appealIf it failsRelated