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
brandand acampaign. - Toll-free verification, for toll-free numbers. Needs the
tollFreedetails.
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
messageFlowwith a link to the page where people opt in - opt-in, opt-out and help keywords
- 2 distinct sample messages (
sample1andsample2, 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:
optInWorkflowImageUrlsneeds at least one screenshot of the opt-in form itself. Upload the image withPOST /v1/sms/opt-in-proof(PNG, JPG, WebP, GIF or PDF, up to 4 MB) and pass the URL it returns.messageVolumeis a monthly tier, one of10,100,1,000,10,000,100,000,250,000,500,000,750,000,1,000,000,5,000,000or10,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:
status | Meaning | What to do |
|---|---|---|
pending | Submitted 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. |
approved | The registration is live; every number attached to it can send. | Start sending, or attach more numbers. |
rejected | The carrier declined it; declineReason says why. | Fix and appeal (below). |
You may also see these statuses:
requestedandchanges_requested: pre-submission review states. Answer a change request withPOST /v1/sms/registrations/{id}/respond.deactivated: terminated registrations. The list hides them unless you passincludeDeactivated=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,campaignandtollFreefield. - Add a number to an existing registration and Appeal a rejected campaign: every field.
- SMS rates: registration fees and per-segment prices.