#A2P registration
Register with the carriers (A2P 10DLC) over the API so your texts deliver at full throughput.
The flow: register a brand, confirm the payment if you got a client_secret, then poll GET /v1/registration every 15 to 60 minutes (approval takes days).
Details
Add more campaigns or vet the brand later if you need them.
What you can send at each step (reviews run in sequence, so times add up).
Details
- Before you register: a trial's free sends end with
402 SANDBOX_LIMIT. Unregistered accounts created since June 23, 2026 can send 500 texts per 30-day cap window (Sending caps). - Brand submitted: the brand is reviewed first. Its campaign waits as
awaiting_brandand isn't submitted yet. - Brand approved: Ready submits the campaign for review, under warm-up limits.
- Campaign approved, sending from a paid number: full volume (campaigns added later have their own warm-up).
Details
- A negotiated setup price replaces these (never more). A new number means sending
preferred_area_code. - Your first campaign is created with the brand and has no setup fee; each extra campaign is $20 a month.
- Brand and campaign fees start when you pay the setup fee (for a campaign with no setup fee, when you submit it). The first 30 days are included, so the first monthly charge comes 30 days later. It doesn't wait for carrier approval.
With a test key, nothing is registered or charged, only format checks run, and responses aren't the live shape.
Details
Skipped: the ownership, EIN-to-name match (ein_name_mismatch), duplicate, brand-first (brand_required_first) and vetting-state checks. Still run: invalid_ein, address_incomplete, business_description_required, consent_required. Test responses are wrapped in data: brand and campaign return registration_id: null, confirm returns only test and note, the other calls return reg_id (see Test keys).
:id in these paths is a registration_id: the brand.id or campaign.id from GET /v1/registration, not a carrier id.
Don't retry a brand or campaign POST after a timeout: poll GET /v1/registration first.
Details
Writes need manage_brands, which an owner's key has. Error shapes vary: branch on code (or error_code) when present, otherwise on error, which holds a machine code for some of them.
#Get registration requirements
/v1/registration/use-casesValid use cases, required fields and prices. Call this first.
Returns
data.use_casesstring[]- Valid campaign
use_casevalues. data.brand_required, campaign_requiredstring[]- Fields a request must include; one without them is refused.
data.auto_filledobject- Ready fills these in. Leave them blank.
data.pricing_usdobjectbrand.setupis the new-number price; keeping your number (nopreferred_area_code) is $30.
Errors
None specific.
#Register a brand
/v1/registration/brandRegisters your business and creates its first campaign, so most accounts never call Add a campaign. Paid.
Details
Its use case isMIXED. It's named after your business and submitted automatically once the brand is approved.Body parameters
company_namestringRequired- Legal name. Alias:
business_name. einstringRequired- 9 digits:
12-3456789or123456789.Details
reg_numberis an alias. It can't be changed later over the API. business_descriptionstringRequired- What the business does, in one or two sentences.
Details
For exampleFamily dental practice in Austin. consentbooleanRequiredtrueattests prior express written consent to text recipients.entity_typeenumCarriers need itDefaultPRIVATE_PROFITPRIVATE_PROFITPUBLIC_PROFITNON_PROFITGOVERNMENT
industrystringCarriers need itstreet, city, state, zipstringRequired- Business address.
email, phone, first_name, last_namestringCarriers need it- Contact person;
phonein E.164. preferred_area_codestringOptional- Buys a new number in this 3-digit area code ($35).
number_choiceenumOptionalnew(buy inpreferred_area_code) orkeep(current number).Details
Defaults tonewwith an area code, otherwisekeep. Other values aren't rejected yet, and are billed $35 without buying a number.
More parameters (5)
websitestringOptional- Leave blank. Ready hosts a compliant opt-in page for your brand and registers that URL; anything you send is replaced.
dbastringOptional- Doing-business-as name.
countrystringOptionalDefaultUSphone_number_idintegerOptional- Which of your numbers to keep.
transfer_confirmbooleanOptional- Send
trueafter a409 needs_transfer_confirmto move the number from its other brand.
Returns
data.charged, amount, card_brand, card_last4, registration_id- Card on file: charged now (
amountin cents). data.client_secret, amount, monthly, label, has_card- No card, or declined (
has_card: true): confirm with Stripe.js, then Confirm a payment.Details
Pass Confirm a payment thepayment_intent_id: the part ofclient_secretbefore_secret_. Theregistration_idcomes back from that call. data.zero_feeboolean- No fee due: submitted immediately.
Details
For example a negotiated $0.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | Missing company_name or ein, or bad preferred_area_code (error). |
| 400 | invalid_ein | In code. |
| 400 | address_incomplete | error names what's missing (code). |
| 400 | business_description_required | In code. |
| 400 | preferred_area_code_required | new without an area code (error). |
| 400 | consent_required | Not boolean true. |
| 400 | ein_name_mismatch | In error, with title, message, steps. |
| 409 | duplicate_brand | See existing_reg_id. |
| 409 | needs_transfer_confirm | A flag, not a code. Resend with transfer_confirm: true. |
| 409 | paid_not_filed | Don't pay again; contact support. |
| 409 | area_code_unavailable | See alternatives. |
| 429 | duplicate_submission | Repeat within 2 minutes. Don't retry. |
| 202 | payment_processing | Still processing. |
| 502 | payment_unknown | Don't resubmit; poll GET /v1/registration. |
| 503 | payments_unavailable | Nothing charged. |
#Add a campaign
/v1/registration/campaignAdds a campaign with another use case (your brand already has one). Consent is given once, on the brand.
Body parameters
campaign_namestringOptionalDefault<business name> Campaignuse_caseenumRequired- One of the use cases, case-insensitive.
brand_idintegerOptional- The brand's
registration_id.Details
Send it when the brand is already approved so the campaign goes straight to review; otherwise it waits asawaiting_brand. campaign_descriptionstringOptional- Leave blank: Ready writes it from your
use_caseand business name.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | Missing use_case (read error). |
| 400 | brand_required_first | No brand yet (one awaiting payment counts). |
| 400 | invalid_use_case | valid lists them. |
| 409 | duplicate_use_case | Use case taken (existing_reg_id, existing_campaign_name). The first campaign is MIXED. |
| 429 | duplicate_submission | Repeat within 2 minutes. |
#Confirm a payment
/v1/registration/confirmAfter Stripe.js confirms a client_secret, this submits the registration now. Safe to call twice.
Body parameters
payment_intent_idstringRequired- The part of
client_secretbefore_secret_.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | payment_intent_id is required. |
| 400 | payment_not_completed | Payment failed (status). |
| 202 | payment_processing | Still processing; retry shortly. |
| 404 | payment_not_found | Not a payment on your account. |
| 503 | payment_check_unavailable | Couldn't check the payment. Retry. |
| 503 | payments_unavailable | Payments down. Retry later. |
#Get registration status
/v1/registrationYour most recent brand and campaign, and whether you can send at registered rates.
Returns
data.brand, data.campaignobject | null- Most recent only:
status,carrier_status,approved_at.Details
carrier_statusis the carrier's own text, such asVERIFIED,VETTED_VERIFIED,UNVERIFIED,REGISTERED. A rejected one addsrejection_reasonandrejection_help. statusstringnot_started,pending_payment,pending,awaiting_brand,approvedoractive,rejected,suspended,cancelled(alsocanceled),deregistered,expired,payment_declined. Treat unknown values as "not sending".data.can_send_registeredbooleantrueonce the carriers approve your campaign. Registered pricing starts earlier, at paid submission.
Errors
None specific.
#Get rejection details
/v1/registration/deniedThe latest rejected registration, the carrier's reasons and a suggested fix.
Returns
data.deniedbooleanfalse(and nothing else) when nothing is rejected.data.reg_id, type, nameinteger, string, string- The registration to resubmit.
data.reasons, reasonstring- The carrier's text, and the same in plain English.
data.ai_fixobject | null- Suggested changes, when we have them.
Errors
| HTTP | Code | When |
|---|---|---|
| 500 | denied_status_unavailable | Couldn't load the details. Retry in a minute. |
#Resubmit a registration
/v1/registration/:id/resubmitResubmits a rejected registration, optionally with corrected fields.
Path parameters
idintegerRequired- The rejected registration's id.
Headers
Idempotency-KeystringRecommended- Resubmitting deregisters and re-registers with the carrier.
Body parameters
fieldsobjectOptional- Corrections. Only these keys apply:
company_name,dba,website,industry,entity_type,use_cases,campaign_description,street,city,state,zip,email,phone,first_name,last_name.Details
Others, and empty strings, are ignored, includingein: to change the EIN, contact support.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_field | fields isn't an object. |
| 404 | not_rejected | Not rejected. |
| 500 | resubmit_unconfirmed | Don't resubmit yet; poll GET /v1/registration. |
| 502 | no code | Carrier refused (error and code). |
#Vet a brand
/v1/registration/:id/vetExternal vetting raises your brand's carrier score and daily throughput. Paid, one time.
Path parameters
idintegerRequired- The brand's registration id.
Body parameters
vetting_typeenumOptionalDefaultSTANDARDSTANDARDorENHANCED, case-insensitive.Details
A brand already vetted Standard can upgrade to Enhanced for the $100 difference (upgrade: true). If vetting can't be submitted, the fee is refunded automatically.
Returns
Top level, not under data.
charged, amount_cents, vetting_type, upgrade, card_last4, payment_intent_id- Card on file: charged now.
client_secret, has_card, payment_intent_id- No card: confirm with Stripe.js, then Confirm the vetting payment with this
payment_intent_id.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | vetting_type must be STANDARD or ENHANCED. |
| 400 | reg_not_found | Not your brand (error). |
| 400 | not_a_brand_registration | Id is a campaign (error). |
| 400 | brand_not_linked_to_infobip | Brand not yet with the carriers (error). |
| 403 | forbidden | Only the registering account can vet (error). |
| 409 | already_vetted | The same tier or a lower one again. |
#Confirm the vetting payment
/v1/registration/:id/vet/confirmApplies the vetting after a client_secret payment. Safe to call twice.
Path parameters
idintegerRequired- The brand's registration id.
Body parameters
payment_intent_idstringRequired- From Vet a brand.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | payment_intent_mismatch | The payment isn't for this vetting. |
| 400 | payment_not_succeeded | Not paid yet (202 while processing; error). |
| 400 | could_not_retrieve_payment_intent | No such payment. |
| 409 | payment_refunded | That payment was refunded. |
| 503 | could_not_retrieve_payment_intent | We couldn't check the payment. Don't pay again; retry. |
Get a brand's carrier limits
/v1/registration/:id/limitsRead carrier-assigned SMS limits for the campaigns belonging to a brand. No recipient validation is needed. Use the Ready brand registration ID, such as data.brand.id from GET /v1/registration, rather than a carrier ID or a campaign blast ID.
Authenticate with X-API-Key. The brand must be visible in the key's account or selected sub-account. For a client workspace, use X-Sub-Account-Id with your agency key. The call is read-only and has no charge.
Query parameters
| Parameter | Purpose |
|---|---|
campaign_id | Optional Ready campaign registration ID. Return only that campaign under this brand. |
after_id | Optional pagination cursor. Return campaign registrations after this ID. Each page holds up to 10 campaigns. |
Returns
data.brand contains id, name, and status. Each entry in data.campaigns has its registration id, name, status, source, checked_at, and carriers:
| Carrier | Assignment |
|---|---|
| AT&T | status, message_class, sms_per_minute, sms_per_second, and scope: "campaign". Fractional rates are preserved, such as 75 per minute = 1.25 per second. |
| T-Mobile | status, tier, daily_messages, and scope: "brand". This is a shared daily allowance. Do not add the same brand allowance across campaigns. |
| Verizon | status, sms_per_second, and scope: "campaign". A null rate means the carrier did not report one, not unlimited sending or instant arrival. |
source: "carrier_assignment" means Ready read the carrier's assignment. Results are cached for up to 60 seconds; checked_at records that read. A campaign not yet registered has source: "not_registered", checked_at: null, and carriers: null.
When data.has_more is true, call again with after_id=data.next_after_id. These assignments describe allowed capacity, not available daily headroom or measured sending speed. Check approval and carrier status, use GET /v1/registration/throughput for account usage and warm-up restrictions, and allow for concurrent traffic and delivery time.
Errors
400: an invalid ID or cursor. 404 registration_not_found: the brand is not visible to this account, or the selected campaign does not belong to it. 503 throughput_unavailable: the database or carrier assignments could not be read. A failure does not return guessed limits.
#Get throughput
/v1/registration/throughputLive sending capacity: 24-hour volume and rejections, T-Mobile daily cap and tier, warm-up state.
Returns
data.total_24h, rejected_24h, reject_pctinteger, integer, number- Last 24 hours: messages, rejections (messages, not segments), percent rejected.
data.throttledbooleantrueat 50 or more rejections and 5% or more of the last 24 hours.Details
Thendaily_loss_usdestimates what they cost, andbrandnames the brand to vet (cta:vet,upgradeormaxed).data.tmobileobject | nulldaily_cap,tier,next_cap,sent_today,pct,coverage,usage_reliable. Readsent_todayonly whenusage_reliableistrue.Details
The ladder is 2,000, 10,000, 40,000, 100,000, 200,000;next_capisnullat the top. The whole object isnulluntil you have a T-Mobile tier.data.rampobject- Warm-up state (below).
cachedboolean- Refreshed at most once a minute; a cached answer has
"cached": true.
Warm-up limits
500 texts from brand approval until your first campaign is approved, and 500 for a later campaign's first 2 business days. Over the cap: 409 RAMP_CAP_REACHED. While ramp.in_warmup is true, pace within ramp.remaining.
Details
Full volume unlocks once the first campaign is approved. The later-campaign cap applies to an account that's already live. ramp.in_warmup is false once you're past it. ends_at is an estimate. If more than one warm-up runs at once (a brand ramp and a newly approved campaign), this reports the one you're most limited by, and ramp.campaign_id names it (null for the brand-wide ramp).
Errors
| HTTP | Code | When |
|---|---|---|
| 503 | throughput_unavailable | Couldn't be read. Never a fake all-clear. |
#Rename a registration
/v1/registration/:id/nameA Ready dashboard label; nothing changes with the carriers.
Path parameters
idintegerRequired- The registration id.
Body parameters
namestringRequired- Up to 80 characters; longer is cut.
Details
It isn't returned byGET /v1/registration, which shows the carrier campaign name.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | name is required. |
| 404 | registration_not_found | No registration with this id. |
#Cancel a registration
/v1/registration/:idCancels the registration and its monthly fee. The setup fee isn't refunded.
Path parameters
idintegerRequired- The registration id.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | registration_approved | Approved; contact support. |
| 403 | no code | Owner only. |
| 404 | registration_not_found | No registration with this id. |
| 409 | registration_changed | Status just changed; nothing removed. |
| 502 | subscription_cancel_failed | We couldn't confirm the monthly fee stopped. |