API
A REST API over HTTPS for SMS, contacts, billing, and webhooks — plus a Zapier app and an MCP server for AI agents. Every response is JSON with a top-level success boolean.
Overview
All endpoints are relative to the production base URL. There is no sandbox — all documented endpoints are production.
https://api.tryready.com
Success responses include a data field; error responses include an error string. Requests and responses are JSON (Content-Type: application/json).
Authentication
Two methods are supported. Use API keys for direct server-to-server calls you own; use OAuth 2.0 for third-party integrations (Zapier, partner apps).
API keys
Generate keys in Settings → Integrations ("Generate an API key"). Pass either as a bearer token or an X-API-Key header.
Authorization: Bearer rsms_your_api_key_here X-API-Key: rsms_your_api_key_here
Keys prefixed rsms_ are general API keys; keys prefixed zap_ are Zapier-scoped keys.
Walkthrough: create an API key3 steps
- Open Settings (the gear, top-right) → Integrations.
- Find the developer / API section and click Generate new key.
- Copy the key — it's shown once and starts with
rsms_. Send it asAuthorization: Bearer <key>or anX-API-Keyheader on your requests.
STOP and other opt-outs automatically — see Compliance.OAuth 2.0
Authorization-code flow used by the published Zapier integration. Tokens from /zapier/oauth/token are JWTs — pass them as Authorization: Bearer <token>. See Zapier surface.
Errors
Errors return a non-2xx status and a JSON body with an error string and, where useful, a stable error_code you can branch on:
{
"success": false,
"error": "This contact replied STOP and is opted out.",
"error_code": "opt_out_locked"
}HTTP status codes:
400— bad request (missing/invalid field).error_code:invalid_number,invalid_url,MESSAGE_TOO_LONG401— missing or invalid credentials402— insufficient balance for a paid action.error_code:insufficient_credits403— blocked / not permitted.error_code:opt_out_locked(recipient replied STOP),global_blocklist(recipient blocked platform-wide),permission_denied404— resource not found429— rate limited (back off + retry)502— carrier rejected the message outright (error_code:OTP_NOT_DELIVEREDand similar)
2xx means (important). A success on POST /sms/send means the message was accepted for sending — status: "queued" — not that it was delivered. Opt-outs and platform blocklists are rejected up front with a 403 (so you know immediately). But a number we can't reach — e.g. a landline — is only known once the carrier reports back. To confirm real delivery, read the delivery status: poll GET /sms/logs / GET /v1/messages (or subscribe to the message_delivered / message_failed webhooks). Delivery status values: queued → delivered, or failed / undelivered.Conventions
Rules that apply across every endpoint.
Pagination
List endpoints — /contacts, /sms/logs, /conversations — take limit and offset query params and return the page in data. Keep requesting until you get a short or empty page.
Rate limiting
A 429 means you're going too fast — back off and retry with exponential backoff. Authentication and one-time-code endpoints are tightly limited (a handful of attempts per 15 minutes) and bulk exports are capped per hour. Sending isn't request-quota'd — it's gated by your credit balance.
Retries & idempotency
/sms/send is not idempotent. If a request times out the message may already be queued, so a blind retry can double-send. Before retrying, confirm with /sms/logs (or key off the returned message_id) rather than re-POSTing.SMS
Send messages, query history, and generate AI-written copy.
Body
+15551234567); we resolve it to the number's id for you. Use this or from_phone_number_id.from and Ready auto-picks the right number for this contact.local (match the recipient's area code — the default), even / deliverability (spread across your numbers, least-loaded first), or random. Ignored if from_phone_number_id is set.Example
curl -X POST https://api.tryready.com/sms/send \
-H "Authorization: Bearer rsms_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"message": "Hello from Ready!"
}'Response 200
{
"success": true,
"data": {
"message_id": "3f0a9c2e-7b41-4e8a-9c1d-2b6f5a0e1d34",
"segments": 1,
"status": "queued",
"credits_remaining": 4823
}
}status starts as queued; the carrier confirms delivered / failed within ~5–30s. Use message_id to poll GET /sms/logs or GET /v1/messages, or subscribe to delivery webhooks. Add media_url to send as MMS. Cost = segments × (your per-segment rate + the per-segment carrier fee) (both returned by GET /v1/account).
Up to 100 recipients per call. Each recipient runs the full send path independently (opt-outs, blocklist, and per-message billing all apply per recipient), so you get an accurate per-recipient result.
Body
Response 200
{
"success": true,
"data": {
"total": 2,
"sent": 1,
"failed": 1,
"results": [
{ "to": "+15551234567", "status": 200, "sent": true, "message_id": "3f0a9c2e-…", "segments": 1 },
{ "to": "+15559876543", "status": 403, "sent": false, "error": "opted out", "error_code": "opt_out_locked" }
]
}
}Query
inbound or outbound.Body
Returns 5 message variants. Costs 5 credits (requires available balance).
Contacts
Create, read, update, and delete contacts.
Query
Body
Send any updatable fields (first_name, last_name, email, tags, pipeline_stage).
Permanently removes the contact and its conversation history.
Conversations
The Inbox is one thread per contact. Read threads and their messages here; to reply, send to the contact with POST /sms/send — it lands in the same thread.
Query
The ordered message history for one conversation.
Look up the conversation for a given contact id.
Body
Open or close the thread in the workflow.
/unread, /archive, /unarchive, and /star follow the same shape.
Suppression & scrubbing
Ready suppresses and scrubs automatically — there's no endpoint to opt someone out or to scrub a list. Instead, every contact carries its compliance state, which you read back through /contacts.
Opt-outs are automatic
Inbound STOP / UNSUBSCRIBE (and the other standard keywords) are honored the moment they arrive: the contact is locked and future sends to it are blocked. You're also notified via the contact_opted_out webhook.
TCPA litigator scrubbing
Known-litigator scrubbing runs automatically on import and before sends. Choose which categories are enforced with GET / POST /compliance/scrub-categories; flagged contacts are marked is_litigator with a tcpa_status.
Reading suppression state
Each contact from /contacts carries opt_out_locked, status (opted_out), dnd_sms, opt_out_source, opt_out_at, is_litigator, tcpa_status, and sms_invalid. To pull just a suppression set, filter the list:
Everyone who replied STOP or is marked DND for SMS (opt_out_locked / status='opted_out' / dnd_sms).
The broadest suppression set — opt-outs plus email/call DND, TCPA litigators, and invalid numbers.
Phone numbers
List, search, buy, and release the numbers on your account — each returns clean JSON. Buying is instant for in-stock area codes.
Response 200
{
"success": true,
"data": [
{ "id": 8412, "phone_number": "+14155551638", "status": "active", "created_at": "2026-07-29T00:12:04Z" }
]
}Query
Response 200
{ "success": true, "available": true, "numbers": [ { "phone_number": "+14155550200" } ] }Body
POST /phone-numbers/checkout (charges your card on file).1.
POST /phone-numbers/checkout → charges your card on file, returns a payment_intent_id.2.
POST /v1/numbers with { area_code, payment_intent_id } → provisions the number.Each
payment_intent_id provisions exactly one number.Response 200
{
"success": true,
"instant": true,
"id": 2647,
"phone_number_id": 2647,
"phone_number": "+14155551638",
"pending_provisioning": false
}Instant provisions (covered area codes) return the number's id right away — use it as from_phone_number_id. Async provisions omit id (the number is created a moment later) and return an infobip_request_id to match via GET /v1/numbers. Out-of-stock area codes return a 400 with alternatives.
Frees the number and stops its monthly fee. Returns { "success": true }.
Query
inbound or outbound.next_before_id from the previous page.Response 200
{
"success": true,
"data": [
{
"id": "3f0a9c2e-7b41-4e8a-9c1d-2b6f5a0e1d34",
"direction": "outbound",
"from": "+14155551638",
"to": "+15551234567",
"body": "Hello from Ready!",
"status": "delivered",
"segments": 1,
"cost_usd": 0.0245,
"media_url": null,
"created_at": "2026-07-29T00:12:10Z",
"delivered_at": "2026-07-29T00:12:14Z"
}
],
"has_more": false,
"next_before_id": null
}Account & pricing
Read your balance and the exact rates you're billed at — so you can compute cost client-side.
Response 200
{
"success": true,
"data": {
"balance_usd": 481.20,
"balance_cents": 48120,
"plan": null,
"sms_rate_usd_per_segment": 0.014,
"carrier_fee_usd_per_segment": 0.0045
}
}Cost of a message = segments × (sms_rate_usd_per_segment + carrier_fee_usd_per_segment). These are your account's actual rates (including any negotiated rate).
Free — checks your own opt-out list. Sends to opted-out numbers are already blocked server-side (403 opt_out_locked); use this if you want to know before sending.
Response 200
{ "success": true, "data": { "phone": "+15551234567", "opted_out": false, "since": null } }Paginated list of every number that opted out on your account (limit, default 100).
A2P registration
Register your business with the carriers (A2P / 10DLC) so your texts deliver at full throughput — end-to-end over the API. The flow: register a brand → register a campaign → confirm payment → poll status. Optionally vet the brand to raise your limits. Registration is paid, so a submit returns a payment_intent_id you confirm the same way you buy a number.
rsms_test_ key simulates every write below — it validates your request and returns { "test": true, "charged": false } without creating a registration or charging a card.Everything you need to build a valid request — call this first.
Response 200
{
"success": true,
"data": {
"use_cases": ["MARKETING","CUSTOMER_CARE","MIXED","DELIVERY_NOTIFICATIONS","ACCOUNT_NOTIFICATIONS","FRAUD_ALERTS"],
"brand_required": ["company_name","ein","entity_type","industry","website","street","city","state","zip","email","phone","first_name","last_name"],
"campaign_required": ["campaign_name","use_case","campaign_description"],
"pricing_usd": { "brand": { "setup": 35, "monthly": 10 }, "campaign": { "setup": 0, "monthly": 20 } },
"consent_required": true
}
}Body
true to attest you have prior express written consent to text your recipients. requiredResponse 200
{
"success": true,
"data": { "client_secret": "pi_..._secret_...", "amount": 3500, "registration_id": 481 }
}Confirm the client_secret with Stripe (or call POST /v1/registration/confirm), then poll GET /v1/registration.
Body
400 brand_required_first.Confirms a card payment so the registration submits immediately (the payment webhook also does this async). Body: { payment_intent_id }. Returns { "success": true, "registration_id": 481, "type": "brand" }.
Poll this to know when you're approved and clear to send at registered rates.
Response 200
{
"success": true,
"data": {
"brand": { "id": 481, "status": "approved", "carrier_status": "VETTED_VERIFIED", "approved_at": "2026-07-28T20:00:00Z" },
"campaign": { "id": 482, "status": "rejected", "carrier_status": null, "approved_at": null,
"name": "Dealership outreach", "rejection_reason": "Sample message lacks opt-out language" },
"can_send_registered": false
}
}status: not_started · pending · approved · rejected. Rejected regs include a rejection_reason — fix and POST …/resubmit. can_send_registered flips true once the campaign is approved.
The latest rejected reg with carrier reasons, AI ai_fix suggestions, and the reg_id to resubmit. Returns { "denied": false } when nothing's rejected.
Fix and resubmit reg :id. Optional body { fields: {…} } overrides. Returns the new status.
External vetting scores your brand higher with the carriers → higher daily throughput. :id is the brand's registration id.
Body
STANDARD or ENHANCED (higher tier, higher limits). Defaults to STANDARD.client_secret + amount_cents; confirm with POST /v1/registration/:id/vet/confirm { payment_intent_id }. An already-Enhanced brand returns 409 already_vetted.Body: { payment_intent_id }. Applies the vetting to the brand.
Look up a business by name to confirm its EIN + address before submitting a brand (reduces rejections). Body: { company_name, state?, street?, city?, zip? }. Rate-limited.
24-hour volume, rejected segments (+ estimated $ loss), your T-Mobile daily cap / tier / next tier, and your warm-up ramp state. Use it to pace sends and to decide whether to vet.
Response 200
{
"success": true,
"data": {
"total_24h": 1840, "rejected_24h": 12, "reject_pct": 0.7,
"tmobile": { "daily_cap": 2000, "tier": "T", "next_cap": 4000, "sent_today": 640, "pct": 32 },
"ramp": { "in_warmup": true, "sent": 180, "cap": 500, "remaining": 320, "ends_at": "2026-08-04T13:00:00Z" }
}
}New campaigns warm up after approval — a capped send limit for the first couple of business days. ramp.in_warmup is false once you're past it; while true, pace within ramp.remaining to avoid warm-up rejections.
Body: { name }. Display-name only — doesn't affect carrier status.
Cancels the registration and stops its subscription. An approved registration can't be deleted — contact support.
Billing
Query credit balance and transaction history.
Returns current balance and usage summary for the account.
Paginated credit transactions (top-ups and usage).
Webhooks
Ready POSTs events to a URL you own as they happen — new replies, deliveries, opt-outs and more. Configure it in Settings → Integrations → Webhooks, or entirely over the API:
Body
https:// endpoint. requiredinbound_reply, message_delivered, message_failed, contact_opted_out. Omit for all.Response 200
{ "success": true, "data": { "url": "https://you.com/hook", "events": ["inbound_reply","message_failed"], "enabled": true } }Returns your current url, subscribed events, and enabled flag.
No code required to receive them — you just need a URL that accepts an HTTP POST.
Events
Payload
Every webhook is a JSON POST shaped { event, timestamp, data }:
{
"event": "inbound_reply",
"timestamp": "2026-07-25T14:30:00.000Z",
"data": {
"message_id": 12345,
"contact_id": 101,
"from": "+15551234567",
"to": "+15559876543",
"body": "Yes, I'm interested!",
"segments": 1
}
}Verifying the signature
Every request carries an X-Signature: sha256=<hash> header — the HMAC-SHA256 of the exact raw request body, keyed with your webhook signing secret (shown in Settings → Integrations). On your endpoint, recompute hex(hmac_sha256(secret, rawBody)) and constant-time compare it to the value after sha256=; if they match, the request genuinely came from Ready. Treat the secret like a password — regenerating it takes effect immediately, so update your receiver at the same time.
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET)
.update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(
Buffer.from(req.headers['x-signature'] || ''),
Buffer.from(expected)
);Using with n8n or Make.com
No server needed. In n8n or Make.com, add a Webhook trigger — it gives you a URL. Paste that URL into Ready's Webhook URL field, pick your events, and Save. Ready then POSTs every matching event into your scenario, where you can route it into thousands of apps with no code.
Zapier
Ready has a published Zapier app — connect 2,000+ apps with no code. Add Ready in Zapier, authorize with OAuth, then build Zaps:
- Triggers — new message, new contact, new conversation, opt-out, delivery status, pipeline changes.
- Actions — send SMS, create/update a contact, add/remove tags, enroll in a drip.
- Searches — find a contact by phone or email.
Under the hood Zapier uses Ready's dedicated OAuth + webhook surface (/zapier/oauth/authorize, /zapier/oauth/token, /zapier/oauth/test, plus subscribe/unsubscribe hooks) — you rarely call these directly.
MCP (AI agents)
Ready runs a full MCP (Model Context Protocol) server so AI agents — Claude, Cursor, Windsurf, ChatGPT, and ClickUp — can operate your account in natural language: send SMS, manage contacts and conversations, build and send campaigns, enroll drips, check number deliverability, and read billing. It exposes roughly 60 tools.
Endpoint
https://api.tryready.com/mcp
Streamable HTTP with JSON-RPC 2.0 — POST /mcp for calls, GET /mcp for the SSE stream; sessions are tracked via the Mcp-Session-Id header.
Connecting
Authenticate with OAuth 2.0 (PKCE — your client walks you through a Ready sign-in) or pass an rsms_ API key as a bearer token (see Authentication). In an MCP-aware client, add Ready as a remote server:
{
"mcpServers": {
"ready": {
"url": "https://api.tryready.com/mcp",
"headers": { "Authorization": "Bearer rsms_your_api_key_here" }
}
}
}Clients that support OAuth discovery can connect with no key — they'll be prompted to sign in. Recognized clients include Claude, Cursor, Windsurf, ChatGPT, and ClickUp.