readyDocs Open the appOpen the app

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.com becomes https://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 as account_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"

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

TwilioOn Ready
POST .../Messages.jsonSends. Form-encoded or JSON. Answers 201 with Twilio’s message fields.
GET .../Messages/{Sid}.jsonReads one message: its status, error_code and error_message.
To, BodyTo is required. So is a Body or a MediaUrl: a picture can go without text.
FromOne of your Ready numbers. Leave it out and Ready picks one.
MediaUrlOne image URL per message. Two or more are refused, and nothing is sent.
StatusCallbackSupported: see Status callbacks. It must be a public http or https URL.
I-Twilio-Idempotency-Token headerUsed as the Idempotency-Key: a retry with the same token gets the first answer and sends nothing twice.
SendAt, ScheduleTypeRefused, and nothing is sent. Ready won’t send now a text you meant to schedule.
consent_attestedNot 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 fieldsIgnored. Without a From, Ready picks the number.
Response sidReady’s message id, the same one as in GET /v1/messages/:id and webhooks. It doesn’t start with SM.
Response priceAlways null. Ready charges your balance; GET /v1/messages lists cost_usd.
Response statusqueued 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 codeWhen
20003A missing or wrong API key (HTTP 401)
20404No message with that sid on your account
20429Too many requests
21211Invalid To number
21602 / 21604Body / To missing
21606From isn’t one of your numbers
21609StatusCallback isn’t a public URL
21610The recipient opted out
21614To is a landline
21617 / 21620Body 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.

ErrorCodeWhy
30003The handset is off or out of reach
30004The recipient or their carrier blocks it
30005The number doesn’t exist
30006The number can’t get texts (a landline)
30007The carrier filtered the content
30008Any 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 with POST /sms/schedule instead), more than one MediaUrl, and listing or deleting messages through the Twilio paths. List with GET /v1/messages.
  • A status callback for a text held for quiet hours. Read its status with GET /v1/messages once it sends.
  • Importing old message history. It stays in Twilio.

Keeping your numbers? Port them under Numbers → Port numbers: Move your numbers to Ready.