ready. Try Ready →Sign up

#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
  1. 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).
  2. Brand submitted: the brand is reviewed first. Its campaign waits as awaiting_brand and isn't submitted yet.
  3. Brand approved: Ready submits the campaign for review, under warm-up limits.
  4. Campaign approved, sending from a paid number: full volume (campaigns added later have their own warm-up).
What it costs. Brand setup $30 keeping your number, $35 with a new one. Monthly: brand $10, campaign $20, plus $5 per number.
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

GET/v1/registration/use-cases

Valid use cases, required fields and prices. Call this first.

  • Cost Free

Returns

data.use_casesstring[]
Valid campaign use_case values.
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_usdobject
brand.setup is the new-number price; keeping your number (no preferred_area_code) is $30.

Errors

None specific.

#Register a brand

POST/v1/registration/brand

Registers your business and creates its first campaign, so most accounts never call Add a campaign. Paid.

  • Auth API key with manage_brands
  • Cost $30 or $35 setup, then $10/month
First campaign.
DetailsIts use case is MIXED. 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-3456789 or 123456789.
Detailsreg_number is an alias. It can't be changed later over the API.
business_descriptionstringRequired
What the business does, in one or two sentences.
DetailsFor example Family dental practice in Austin.
entity_typeenumCarriers need itDefault PRIVATE_PROFIT
  • PRIVATE_PROFIT
  • PUBLIC_PROFIT
  • NON_PROFIT
  • GOVERNMENT
industrystringCarriers need it
street, city, state, zipstringRequired
Business address.
email, phone, first_name, last_namestringCarriers need it
Contact person; phone in E.164.
preferred_area_codestringOptional
Buys a new number in this 3-digit area code ($35).
number_choiceenumOptional
new (buy in preferred_area_code) or keep (current number).
DetailsDefaults to new with an area code, otherwise keep. 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.
countrystringOptionalDefault US
phone_number_idintegerOptional
Which of your numbers to keep.
transfer_confirmbooleanOptional
Send true after a 409 needs_transfer_confirm to move the number from its other brand.

Returns

data.charged, amount, card_brand, card_last4, registration_id
Card on file: charged now (amount in cents).
data.client_secret, amount, monthly, label, has_card
No card, or declined (has_card: true): confirm with Stripe.js, then Confirm a payment.
DetailsPass Confirm a payment the payment_intent_id: the part of client_secret before _secret_. The registration_id comes back from that call.
data.zero_feeboolean
No fee due: submitted immediately.
DetailsFor example a negotiated $0.

Errors

HTTPCodeWhen
400no codeMissing company_name or ein, or bad preferred_area_code (error).
400invalid_einIn code.
400address_incompleteerror names what's missing (code).
400business_description_requiredIn code.
400preferred_area_code_requirednew without an area code (error).
400consent_requiredNot boolean true.
400ein_name_mismatchIn error, with title, message, steps.
409duplicate_brandSee existing_reg_id.
409needs_transfer_confirmA flag, not a code. Resend with transfer_confirm: true.
409paid_not_filedDon't pay again; contact support.
409area_code_unavailableSee alternatives.
429duplicate_submissionRepeat within 2 minutes. Don't retry.
202payment_processingStill processing.
502payment_unknownDon't resubmit; poll GET /v1/registration.
503payments_unavailableNothing charged.

#Add a campaign

POST/v1/registration/campaign

Adds a campaign with another use case (your brand already has one). Consent is given once, on the brand.

  • Auth API key with manage_brands
  • Cost No setup fee; $20/month from submission (first 30 days included)

Body parameters

campaign_namestringOptionalDefault <business name> Campaign
use_caseenumRequired
One of the use cases, case-insensitive.
brand_idintegerOptional
The brand's registration_id.
DetailsSend it when the brand is already approved so the campaign goes straight to review; otherwise it waits as awaiting_brand.
campaign_descriptionstringOptional
Leave blank: Ready writes it from your use_case and business name.

Errors

HTTPCodeWhen
400no codeMissing use_case (read error).
400brand_required_firstNo brand yet (one awaiting payment counts).
400invalid_use_casevalid lists them.
409duplicate_use_caseUse case taken (existing_reg_id, existing_campaign_name). The first campaign is MIXED.
429duplicate_submissionRepeat within 2 minutes.

#Confirm a payment

POST/v1/registration/confirm

After Stripe.js confirms a client_secret, this submits the registration now. Safe to call twice.

  • Auth API key with manage_brands
  • Cost Free

Body parameters

payment_intent_idstringRequired
The part of client_secret before _secret_.

Errors

HTTPCodeWhen
400no codepayment_intent_id is required.
400payment_not_completedPayment failed (status).
202payment_processingStill processing; retry shortly.
404payment_not_foundNot a payment on your account.
503payment_check_unavailableCouldn't check the payment. Retry.
503payments_unavailablePayments down. Retry later.

#Get registration status

GET/v1/registration

Your most recent brand and campaign, and whether you can send at registered rates.

  • Cost Free

Returns

data.brand, data.campaignobject | null
Most recent only: status, carrier_status, approved_at.
Detailscarrier_status is the carrier's own text, such as VERIFIED, VETTED_VERIFIED, UNVERIFIED, REGISTERED. A rejected one adds rejection_reason and rejection_help.
statusstring
not_started, pending_payment, pending, awaiting_brand, approved or active, rejected, suspended, cancelled (also canceled), deregistered, expired, payment_declined. Treat unknown values as "not sending".
data.can_send_registeredboolean
true once the carriers approve your campaign. Registered pricing starts earlier, at paid submission.

Errors

None specific.

#Get rejection details

GET/v1/registration/denied

The latest rejected registration, the carrier's reasons and a suggested fix.

  • Cost Free

Returns

data.deniedboolean
false (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

HTTPCodeWhen
500denied_status_unavailableCouldn't load the details. Retry in a minute.

#Resubmit a registration

POST/v1/registration/:id/resubmit

Resubmits a rejected registration, optionally with corrected fields.

  • Auth API key with manage_brands
  • Idempotent Yes, with Idempotency-Key
  • Cost Free

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.
DetailsOthers, and empty strings, are ignored, including ein: to change the EIN, contact support.

Errors

HTTPCodeWhen
400invalid_fieldfields isn't an object.
404not_rejectedNot rejected.
500resubmit_unconfirmedDon't resubmit yet; poll GET /v1/registration.
502no codeCarrier refused (error and code).

#Vet a brand

POST/v1/registration/:id/vet

External vetting raises your brand's carrier score and daily throughput. Paid, one time.

  • Auth API key with manage_brands
  • Cost $79 Standard, $179 Enhanced, $100 upgrade

Path parameters

idintegerRequired
The brand's registration id.

Body parameters

vetting_typeenumOptionalDefault STANDARD
STANDARD or ENHANCED, case-insensitive.
DetailsA 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

HTTPCodeWhen
400no codevetting_type must be STANDARD or ENHANCED.
400reg_not_foundNot your brand (error).
400not_a_brand_registrationId is a campaign (error).
400brand_not_linked_to_infobipBrand not yet with the carriers (error).
403forbiddenOnly the registering account can vet (error).
409already_vettedThe same tier or a lower one again.

#Confirm the vetting payment

POST/v1/registration/:id/vet/confirm

Applies the vetting after a client_secret payment. Safe to call twice.

  • Auth API key with manage_brands
  • Cost Free

Path parameters

idintegerRequired
The brand's registration id.

Body parameters

payment_intent_idstringRequired
From Vet a brand.

Errors

HTTPCodeWhen
400payment_intent_mismatchThe payment isn't for this vetting.
400payment_not_succeededNot paid yet (202 while processing; error).
400could_not_retrieve_payment_intentNo such payment.
409payment_refundedThat payment was refunded.
503could_not_retrieve_payment_intentWe couldn't check the payment. Don't pay again; retry.

Get a brand's carrier limits

GET/v1/registration/:id/limits

Read 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

ParameterPurpose
campaign_idOptional Ready campaign registration ID. Return only that campaign under this brand.
after_idOptional 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:

CarrierAssignment
AT&Tstatus, 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-Mobilestatus, tier, daily_messages, and scope: "brand". This is a shared daily allowance. Do not add the same brand allowance across campaigns.
Verizonstatus, 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

GET/v1/registration/throughput

Live sending capacity: 24-hour volume and rejections, T-Mobile daily cap and tier, warm-up state.

  • Cost Free

Returns

data.total_24h, rejected_24h, reject_pctinteger, integer, number
Last 24 hours: messages, rejections (messages, not segments), percent rejected.
data.throttledboolean
true at 50 or more rejections and 5% or more of the last 24 hours.
DetailsThen daily_loss_usd estimates what they cost, and brand names the brand to vet (cta: vet, upgrade or maxed).
data.tmobileobject | null
daily_cap, tier, next_cap, sent_today, pct, coverage, usage_reliable. Read sent_today only when usage_reliable is true.
DetailsThe ladder is 2,000, 10,000, 40,000, 100,000, 200,000; next_cap is null at the top. The whole object is null until 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

HTTPCodeWhen
503throughput_unavailableCouldn't be read. Never a fake all-clear.

#Rename a registration

PUT/v1/registration/:id/name

A Ready dashboard label; nothing changes with the carriers.

  • Auth API key with manage_brands
  • Cost Free

Path parameters

idintegerRequired
The registration id.

Body parameters

namestringRequired
Up to 80 characters; longer is cut.
DetailsIt isn't returned by GET /v1/registration, which shows the carrier campaign name.

Errors

HTTPCodeWhen
400no codename is required.
404registration_not_foundNo registration with this id.

#Cancel a registration

DEL/v1/registration/:id

Cancels the registration and its monthly fee. The setup fee isn't refunded.

  • Auth Account owner's API key
  • Cost Free

Path parameters

idintegerRequired
The registration id.

Errors

HTTPCodeWhen
400registration_approvedApproved; contact support.
403no codeOwner only.
404registration_not_foundNo registration with this id.
409registration_changedStatus just changed; nothing removed.
502subscription_cancel_failedWe couldn't confirm the monthly fee stopped.