Most SMS API explainers stop at "send an HTTP request, a text arrives." That part takes about ten minutes. The rest of the work is less obvious: which endpoints you actually need, where the API key lives, what happens when someone replies STOP, how a 180-character message turns into a bill for three segments, and why your texts can get filtered until you've done carrier registration.

This guide is for developers and product managers adding texting to a product for the first time. We make Ready, so the examples use the Ready API, but the concepts apply to any provider. Where a raw CPaaS like Twilio or Telnyx fits better, we say so.

What an SMS API actually is

An SMS API is a web interface that turns an HTTP request into a text message. Your code sends a JSON payload to a URL, and the provider takes care of the parts you don't want to build:

  • Holding phone numbers and connecting to carrier infrastructure
  • Splitting long messages into segments and encoding them correctly
  • Honoring opt-outs and holding sends during quiet hours
  • Passing inbound replies back to your application

The alternative is running your own gateway and carrier connections, which almost never makes sense below very large volumes. An SMS API is the same idea as a payments API: you rent the plumbing and keep your attention on your product.

The common use cases:

  • Transactional texts. Order shipped, appointment reminder, password code.
  • Conversational texts. Two-way support or sales threads.
  • Campaigns. The same message sent to a consented list.

Endpoints: the handful you'll actually call

An endpoint is a URL plus an HTTP method that does one job. Most SMS APIs have dozens of them, but a typical integration uses five or six. Here are Ready's, with the base URL https://api.tryready.com:

What you wantMethod and pathNotes
Send one textPOST /v1/messagesto, body, consent_attested; optional from, media_url (more in the reference)
Send one text to many numbersPOST /v1/messages/bulkAt most 100 recipients per call
Read message historyGET /v1/messagesPaginate with before_id
Check opt-outsGET /v1/opt-outs, GET /v1/opt-outs/:phoneThe list is this account's opted-out contacts; check a number with GET /v1/opt-outs/:phone before any send from another tool
Validate a numberGET /v1/validate/:phoneCatch bad numbers before you store them
Scrub a numberGET /v1/scrub/:phoneChecks TCPA-litigator and DNC lists
Check registration statusGET /v1/registrationShows whether 10DLC is approved
Manage numbers and sub-accountsGET/POST /v1/numbers, GET/POST /v1/sub-accountsFor platforms texting on behalf of customers

If you're building a CRM or vertical SaaS where each of your customers texts their own contacts, the sub-accounts endpoints matter most. Each customer gets its own numbers and its own registration, so one customer's bad list can't damage another's deliverability. The full reference is at docs.tryready.com/api/messages.

API keys: what they are and how to keep them safe

An API key is a long secret string that identifies your account on every request. On Ready it goes in a header:

X-API-Key: your_key_here

The key can send texts that you pay for, so treat it like a database password:

  1. Keep it server-side. Never put it in a mobile app, a browser bundle or a public repo. Anyone who extracts it can send texts on your account.
  2. Store it in an environment variable or a secrets manager, not in source code.
  3. Use separate keys per environment where you can, so a staging bug doesn't send from production.
  4. Rotate the key if it leaks. Assume any key pasted into Slack or a screenshot has leaked.

If your frontend needs to trigger a text, have it call your own backend. Your backend checks the user, then calls the SMS API.

Your first send, step by step

This is the smallest request that works:

curl -X POST https://api.tryready.com/v1/messages \
  -H "X-API-Key: $READY_API_KEY" \
  -H "Idempotency-Key: <a new UUID for each text>" \
  -H "Content-Type: application/json" \
  -d '{"to": "+12125550199", "body": "Your order #1042 has shipped.", "consent_attested": true}'

Three details in that request are worth explaining.

to is in E.164 format. That means a plus sign, the country code and the number. A 10-digit US number is treated as +1, but normalizing numbers yourself avoids surprises.

consent_attested is your promise that the recipient agreed to receive texts. Ready records that attestation for each send: who sent, to how many people and when. It is not a per-contact consent record. Ready doesn't store the time a recipient opted in, the wording they saw or the page they signed up on, so your app needs to keep that. If a complaint ever arrives, your signup records are the evidence that counts. (A contact who signs up on a Ready-hosted opt-in page is the exception: Ready stores that sign-up, with the time, IP address and exact wording, and support@tryready.com can send you a copy.)

The Idempotency-Key header stops duplicate sends. If your request times out and your code retries, a reused key means the text goes out once, not twice. Generate a UUID per logical message and reuse it on every retry. We cover the failure modes in how to stop duplicate texts from your SMS API.

For lists, call POST /v1/messages/bulk in batches of 100 or fewer. Throughput is limited per campaign: a fresh standard 10DLC campaign gets roughly 1 to 4 segments per second, so a large list takes time. Queue the work rather than firing it all in one request. Language-specific walkthroughs are in how to integrate an SMS API into your app and the API quickstart.

Handling inbound: replies, STOP and webhooks

Sending is one direction. Products that only send tend to miss three things on the inbound side.

Replies. When someone texts back, the provider has to tell your app. Ready does this with webhooks: it sends a request to your server when something happens, such as an inbound reply. Event names and payloads are in the webhooks docs, so build against those rather than guessing. You can also poll GET /v1/messages and page backward with before_id. That works fine for a nightly reconciliation job. Replies you receive are billed like sent texts: the per-segment rate plus the carrier fee.

STOP. Inbound STOP and UNSUBSCRIBE are handled automatically, and the opt-out applies across all campaigns. A later send to that number is refused up front and not charged. GET /v1/opt-outs lists this account's opted-out contacts, so use it to show them as unsubscribed in your UI. Otherwise your users will wonder why a send "failed." It isn't a complete suppression list: before you text a number from another tool, check GET /v1/opt-outs/:phone.

Quiet hours. A text addressed to someone during their quiet hours is held and sent later, based on the recipient's area. You're charged only when it actually sends. Campaigns, drip campaigns and automations wait up to 24 hours for the window; a one-to-one or API text is held only if the window opens within 6 hours, otherwise it is refused with 409 QUIET_HOURS_BLOCK and must be resent; a one-to-one reply to someone who texted you in the last 24 hours goes out at any hour.

For a broader comparison of how providers handle the inbound side, see two-way SMS API options.

What it costs, with the segment math

SMS is billed per segment, not per message:

  • A plain-text (GSM-7) message fits 160 characters in one segment.
  • Longer messages split into segments of 153 characters each.
  • One emoji switches the whole message to unicode, which drops the limit to 70 characters, or 67 per segment when split.

Ready swaps curly quotes, long dashes and ellipses for plain versions before sending, so those characters never push you into unicode. Emoji still do.

Worked example (assumptions stated): an order update of 180 plain characters, sent to 1,000 opted-in customers.

  • 180 characters split at 153 is 2 segments, so the send is 2,000 segments.
  • Ready Standard is $0.0075 per segment: 2,000 × $0.0075 = $15.
  • Add 2,000 × $0.0045 = $9 in carrier fee. This is itemized separately on your bill.
  • That's $24 all-in, excluding the compliance surcharge. If you switch on scrubbing or landline validation for the account, each adds $0.003 per segment: 2,000 × $0.003 = $6 each. Both are off by default.

Add one emoji to the same message and 180 characters becomes 3 unicode segments. That's 3,000 segments and $60 before carrier fees, a 50% increase from one character.

Prefer to scrub specific numbers on demand instead of switching it on per segment? The standalone TCPA and DNC litigator scrub is $0.005 per contact. TCPA exposure runs $500 to $1,500 per text, so scrubbing is cheap risk reduction, though it doesn't make you immune to lawsuits.

How Ready's price compares. Twilio runs about $0.0083 per segment, and GoHighLevel's LeadConnector about $0.0083 per message. If you only need raw transport and you're happy to build registration, opt-out sync, quiet hours and scrubbing yourself, a raw CPaaS is a reasonable choice. What Ready adds is that compliance layer, sub-accounts with their own registration, and an app with an inbox and campaigns.

The part tutorials skip: 10DLC registration

In the US, business texts from regular 10-digit numbers must be registered with carriers under A2P 10DLC. You register a brand (who you are) and a campaign (what you send). Unregistered traffic gets filtered, so your test sends may arrive while production traffic quietly doesn't.

On Ready, registration is handled in the app or through the API:

  • Cost at submit: $25, which is the first month. There's no setup fee.
  • Monthly after that: $25/mo for registration, and $2 per number.
  • Approval time: most approvals take 2 to 7 days.

While you wait, an account can send up to 500 texts a month before registering, at its normal per-text rate. After that, it has to register. If you bought your number first, you can send on Ready's shared registration at the same rate while your own registration is in review.

Platforms that register their customers' brands programmatically should read the 10DLC registration API guide. If you're also wiring up AI agents, Ready runs an MCP server that can send texts and manage contacts. MCP has no tools for buying numbers or registering, so those steps stay in the API or the app.

Getting started

The practical order:

  1. Sign up and add a card. To send, you add a card and get a number, which is $2 a month with the first month charged when you get it.
  2. Store your API key server-side.
  3. Send a few one-to-one test texts with idempotency keys. Your first 20 one-to-one texts are free, so use them before you register; they end the moment a registration is paid for. Campaigns are paid from the first text.
  4. Wire up inbound webhooks and read this account's opt-outs into your UI.
  5. Submit registration, then move to bulk sends once it's approved.

If you only need cheap transport and have engineers to build the compliance pieces, a raw CPaaS will do the job. If you'd rather get registration, STOP handling, quiet hours and sub-accounts from one API, the API quickstart gets you from key to first delivered text in a few minutes.