Coming from Twilio
Ready answers Twilio’s Messages API at the same paths. Code that sends with POST /2010-04-01/Accounts/{AccountSid}/Messages.json keeps working when you change two things: the host, and the password. Opt-outs, quiet hours, registration and billing work exactly as they do for POST /v1/messages.
#Change two lines
- Host:
https://api.twilio.combecomeshttps://api.tryready.com. - Credentials: keep Basic auth. The username can be your Account SID or anything else. The password is your Ready API key (
rsms_...) in place of the Auth Token. The key alone decides the account: Ready ignores the username and the{AccountSid}in the path, and echoes it back asaccount_sid.
curl -X POST "https://api.tryready.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Messages.json" \
-u "$TWILIO_ACCOUNT_SID:$READY_API_KEY" \
--data-urlencode "To=+12125550199" \
--data-urlencode "From=+12125550100" \
--data-urlencode "Body=Your order #1042 has shipped." \
--data-urlencode "StatusCallback=https://example.com/twilio/status"
const twilio = require('twilio');
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.READY_API_KEY);
client.api.baseUrl = 'https://api.tryready.com';
const msg = await client.messages.create({
to: '+12125550199',
from: '+12125550100',
body: 'Your order #1042 has shipped.',
statusCallback: 'https://example.com/twilio/status',
});
console.log(msg.sid, msg.status);
The Node example was run against Ready with twilio-node 5.13. Other Twilio libraries work if you can set their API base URL. Otherwise, call the URL directly as the curl example does.
#What’s supported
| Twilio | On Ready |
|---|---|
POST .../Messages.json | Sends. Form-encoded or JSON. Answers 201 with Twilio’s message fields. |
GET .../Messages/{Sid}.json | Reads one message: its status, error_code and error_message. |
To, Body | To is required. So is a Body or a MediaUrl: a picture can go without text. |
From | One of your Ready numbers. Leave it out and Ready picks one. |
MediaUrl | One image URL per message. Two or more are refused, and nothing is sent. |
StatusCallback | Supported: see Status callbacks. It must be a public http or https URL. |
I-Twilio-Idempotency-Token header | Used as the Idempotency-Key: a retry with the same token gets the first answer and sends nothing twice. |
SendAt, ScheduleType | Refused, and nothing is sent. Ready won’t send now a text you meant to schedule. |
consent_attested | Not a Twilio field, but Ready will require "consent_attested": true on every send once it is enforced (what it means). Today a send without it goes through, with a ready_warnings note in the answer. Add it to a JSON or form body now; a Twilio library that only sends Twilio fields can’t, so call the URL directly for those sends. |
MessagingServiceSid and other fields | Ignored. Without a From, Ready picks the number. |
Response sid | Ready’s message id, the same one as in GET /v1/messages/:id and webhooks. It doesn’t start with SM. |
Response price | Always null. Ready charges your balance; GET /v1/messages lists cost_usd. |
Response status | queued on send, then delivered, undelivered (the carrier didn’t deliver it) or failed (Ready stopped it before sending). |
A text held for quiet hours answers status: "scheduled" with sid: null and a ready_warning saying when it sends.
#Errors
A refusal comes back in Twilio’s error shape, {"code", "message", "more_info", "status"}, plus ready_code, which is Ready’s error code. code is Twilio’s number where one means the same thing. Otherwise it is null, and ready_code says what happened.
Twilio code | When |
|---|---|
20003 | A missing or wrong API key (HTTP 401) |
20404 | No message with that sid on your account |
20429 | Too many requests |
21211 | Invalid To number |
21602 / 21604 | Body / To missing |
21606 | From isn’t one of your numbers |
21609 | StatusCallback isn’t a public URL |
21610 | The recipient opted out |
21614 | To is a landline |
21617 / 21620 | Body too long / bad MediaUrl |
If Ready can’t tell whether the carrier took a text, the answer is 202 with ready_code: "send_outcome_unknown". Don’t resend it: check GET .../Messages/{Sid}.json or GET /v1/messages first.
#Status callbacks
Send a StatusCallback with a message, and Ready POSTs Twilio’s form fields there once the message reaches its final status: MessageSid, SmsSid, MessageStatus (delivered or undelivered), SmsStatus, AccountSid, To, From, ApiVersion, and on a failure ErrorCode. Ready doesn’t send the in-between sent status. As with Twilio, a callback that fails isn’t retried. The status stays readable with GET .../Messages/{Sid}.json.
ErrorCode | Why |
|---|---|
30003 | The handset is off or out of reach |
30004 | The recipient or their carrier blocks it |
30005 | The number doesn’t exist |
30006 | The number can’t get texts (a landline) |
30007 | The carrier filtered the content |
30008 | Any other failure |
Callbacks are signed with your webhook secret, the one shown when you save a webhook with PUT /v1/webhooks or in Settings → Developers → Webhooks. They carry two headers:
X-Twilio-Signature, made the way Twilio makes it. Twilio’s own check works if you pass the webhook secret where the Auth Token went:twilio.validateRequest(READY_WEBHOOK_SECRET, signature, url, params).X-Signature: sha256=<hex>, the HMAC-SHA256 of the raw form body, as on every Ready webhook (Verifying the signature).
An account that has never saved a webhook has no secret yet, so its callbacks arrive unsigned. Only want status callbacks? Save a webhook once with PUT /v1/webhooks and "enabled": false. That keeps the URL, sends no account events, and the answer carries your secret.
#Replies and opt-outs
Inbound texts don’t use Twilio’s format. Ready posts a JSON event to the webhook URL you saved, for replies, delivery and opt-outs (Webhooks). If each of your Twilio numbers had its own SMS URL, give each Ready number its URL with PUT /v1/numbers/:id: its replies and delivery statuses go there. Ready handles STOP for you and refuses sends to anyone who opted out. To bring your Twilio opt-out list, see Bringing opt-outs from another provider.
#What Ready doesn’t have
- TwiML. A reply in your webhook’s answer is ignored, and inbound texts don’t arrive in Twilio’s request format.
- Messaging Services,
SendAt(schedule withPOST /sms/scheduleinstead), more than oneMediaUrl, and listing or deleting messages through the Twilio paths. List withGET /v1/messages. - A status callback for a text held for quiet hours. Read its status with
GET /v1/messagesonce it sends. - Importing old message history. It stays in Twilio.
Keeping your numbers? Port them under Numbers → Port numbers: Move your numbers to Ready.