Zernio
APIFor you
Zernio
APIFor you
Platforms

SMS

Overview

Set up

Carrier registrationSender IDs

Messaging

Sending

Reference

Limits & errors
Dashboard
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.


By the end of this page, a US number has a carrier registration in review. You also know how to add more numbers to it without paying again.

Before you start

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
  • 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": true,
  "country": "US",
  "smsCapable": true,
  "mmsCapable": true,
  "needsRegistration": true,
  "alreadyRegistered": false,
  "registrationStatus": null,
  "reusable": null
}

id is the account id the number sends from. isActive is true as soon as SMS is on: the number receives right away, and a US number starts sending once 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)
  • a business phone, for company, non-profit and government brands

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
  • 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 proprietors. Sole-proprietor registrations (sole_prop_10dlc) return awaitingOtp: true. A code is texted to the brand's mobilePhone.

The registration waits until you submit the code 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. 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 these statuses:

  • requested and changes_requested: pre-submission review states. Answer a change request with POST /v1/sms/registrations/{id}/respond.
  • deactivated: terminated registrations. The list hides them unless you pass includeDeactivated=true.

brandStatus, campaignStatus and trustScore are the raw carrier-registry fields. They are 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. 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.

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

Sender IDs

Send one-way international SMS from a brand name like ZERNIO instead of a phone number, with POST /v1/sms/sender-ids and the regular send endpoint.

On this page

Before you startWhat 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