readyDocs Open the appOpen the app

Message log and delivery

Read back how your texts did, and check a text before you send it.

#Export messages

Downloads your message log as a CSV file, newest first, including texts your team members sent. It takes the same filters as the Logs tab in Reports. It streams, so a day of a very large account downloads in one file. An export never comes back silently cut short: a file holds 500,000 messages for each day of the range (at least 5,000,000, at most 50,000,000); past that the file ends with a # Export stopped at line, and a read that fails part way ends it with # export aborted. Narrow the dates or filters and export again.

Parameters

fromdate, query
First day to include, as YYYY-MM-DD, in your account’s time zone.
todate, query
Last day to include, as YYYY-MM-DD. The whole day is included.
daysinteger, query
The last this many days, today included. Used when there’s no from. With no from, to or days, the export covers all your messages.
directionenum, query
inbound or outbound.
statusstring, query
Only messages with this status: queued, delivered, failed or undelivered, the same words as the file’s status column. failed also includes the undelivered ones. sent or pending narrows queued to one of them.
filtersJSON string, query
A list of conditions, each {"field", "op", "value"}. field is status, dir, carrier, number, body, sentvia, channel or campaign. op is is, is not, is any of, is none of, contains, does not contain, is empty or is not empty. A condition Ready doesn’t know is skipped. For sentvia, value is campaign, automation (drip campaigns and automations) or manual. Any other value is skipped.
matchenum, queryDefault and
or to match any condition in filters instead of all of them.
sub_accountstring, query
all for your main account and every sub-account, or one account’s id. Only the owner or an admin of the main account can use it. Adds the sub_account_id and sub_account columns.
Returns

A CSV file (text/csv, named messages.csv) with a header row and one row per message. Columns:

idinteger
Ready’s numeric id for the message.
directionenum
inbound or outbound.
from_numberstring
The number it came from.
to_numberstring
The number it went to.
bodystring
The exact text that went out, with any opt-out line or business name Ready added.
statusstring
queued, delivered, failed, undelivered or unknown, as in the Message object. Other stored words, like received, appear as they are.
segmentsinteger
Segments billed.
sent_atstring
When it was sent.
error_reasonstring
The stable cause of a failure, the same values as on the Message object. Empty when it didn’t fail.
error_detailstring
A readable explanation of the failure. Empty when it didn’t fail.
sub_account_idinteger
Only with sub_account: the account the message belongs to.
sub_accountstring
Only with sub_account: that account’s name.
contact_namestring
The contact’s first and last name. Empty when the number isn’t a saved contact.
campaignstring
The campaign or drip campaign that sent it. Empty for a one-to-one text or a reply.
media_urlstring
The link to the picture or file the message carried. Empty for a plain text.
message_idstring
The message’s public id, the id on the Message object.
channelenum
sms, mms or imessage, as the message log shows it.

Each account can run 6 exports an hour, shared with contact exports and the dashboard. A refused export doesn’t count.

Errors
HTTPCodeWhen
400invalid_fieldA filter was sent twice, like ?status=a&status=b, or sub_account isn’t all or an id.
403permission_deniedYour role doesn’t have view_messages.
403sub_account_scope_forbiddenOnly the owner or an admin of the main account can use sub_account. Also organization_only.
404sub_account_not_foundsub_account isn’t one of your accounts.
422too_many_sub_accountsToo many sub-accounts for all. Pick one.
429rate_limitedOver the hourly export limit. Try again later in the hour.
500no codeThe export failed. Retry in a minute.
503export_timezone_read_failedReady couldn’t read your time zone for a dated export. Retry in a minute.

#Count failures by reason

Counts your failed outbound texts by cause, most common first. This is the "Why texts failed" card in Reports. Counts are exact, not a sample, and are cached for 5 minutes.

Parameters

fromdate, query
First day to include, as YYYY-MM-DD, in your account’s time zone.
todate, query
Last day to include, as YYYY-MM-DD.
daysinteger, query
The last this many days, today included. With no from, to or days, the last 90 days.
filtersJSON string, query
The same conditions as Export messages. A status condition is ignored, since only failures are counted.
matchenum, queryDefault and
or to match any condition in filters.
sub_accountstring, query
all or one account’s id, as in Export messages.
Returns
data.by_reason[].error_reasonstring | null
The stable cause, the same values as error_reason on the Message object. Group and match on this. reason holds the same value.
data.by_reason[].error_detailstring | null
A readable explanation to show.
data.by_reason[].countinteger
Failed texts with this cause.
data.by_reason[].explanationobject | null
title, body, fix_action (like check_recipient or contact_support) and severity (like recipient or compliance). null when Ready has no explanation for the cause.
data.totalinteger
The sum of every count.
Errors
HTTPCodeWhen
400invalid_fieldA filter was sent twice, or sub_account isn’t all or an id.
403sub_account_scope_forbiddenOnly the owner or an admin of the main account can use sub_account. Also organization_only.
404sub_account_not_foundsub_account isn’t one of your accounts.
422too_many_sub_accountsToo many sub-accounts for all. Pick one.
503reason_breakdown_unavailableThe counts couldn’t be loaded, often because the date range is too big. This never means zero failures. Retry, or pick fewer days.

#Get your delivery rate

Your outbound delivery rate for the last 24 hours, 7 days and 30 days, with a count per carrier. Pass a window to get its rate too, and a second window to compare against. The rate counts only texts that reached a carrier and got an answer: delivered out of delivered plus failed. Texts Ready refused before sending are counted in blocked, not as failures. Home and Reports in the app show delivered out of every text sent, so this rate can read higher for the same days.

Parameters

daysinteger, query
A window of the last this many days, 1 to 365. Another value is ignored.
sinceISO 8601 string, query
Start of a window, like 2026-10-01 or 2026-10-01T00:00:00Z. A date alone is a whole day in your account’s time zone. Takes the place of days. A value in another format is ignored.
untilISO 8601 string, query
End of the window. A date alone includes that whole day in your account’s time zone. A window can be up to 90 days. A lone since or until is measured from now.
carrierenum, query
Only texts to this carrier: verizon, att, tmobile, uscellular, sprint, other, international or unknown. Applies to every rate. Another value is ignored.
compare_to_daysinteger, query
A second window to compare with, like days. Also compare_to_since and compare_to_until, like since and until.
Returns
delivery_rate_24hobject
A rate for the last 24 hours, with the fields below. delivery_rate_7d and delivery_rate_30d are the same for 7 and 30 days.
delivery_rate_customobject | null
The rate for your window. null without one.
delivery_rate_compareobject | null
The rate for the comparison window. null without one.
delivery_rate_deltaobject | null
How your window (or the last 7 days, without one) moved against the comparison window: delivered_pct_delta_pp (percentage points), total_volume_pct_change, and interpretation: improving, degrading or flat (within 1 point). null without a comparison window or when either has no answered texts.
carrier_breakdown[]object[]
One row per carrier over your window, or the last 7 days without one: carrier, total, delivered, failed and pending. The counts come back as strings of digits. carrier is one of verizon, att, tmobile, uscellular, sprint, other, international, or (unknown) when no carrier is on record for the number. (unknown) rows cannot be filtered with ?carrier=.
filtersobject
The carrier and window Ready applied, so you can see when a value was ignored.
custom_window_labelstring | null
Your window in words, like last 30d. Also compare_window_label.

Each rate object has:

deliveredinteger
Delivered texts.
failedinteger
Texts a carrier took and couldn’t deliver.
blockedinteger
Texts Ready refused before sending, such as opt-outs or landlines.
pendinginteger
Texts still waiting for a delivery report.
totalinteger
Every outbound text in the window.
delivered_pctnumber | null
Delivered out of delivered plus failed, one decimal. null when none were answered. Also failed_pct.
pending_pctnumber
Pending out of total.
system_failedinteger
Failures on the carrier or network side.
sender_failedinteger
Failures the carrier put down to the message itself, like content filtering.
system_delivered_pctnumber | null
Delivered out of delivered plus system_failed.
notestring | null
A sentence when more than half the texts are still pending, so the rate covers only the rest.
Errors
HTTPCodeWhen
400invalid_fieldA parameter was sent twice.
400invalid_datesince or until isn’t a real date, like 2026-02-30.
400date_range_reverseduntil is before since.
400date_range_too_largeThe window is over 90 days. span_days says how long it was.
400compare_invalid_dateThe same three checks on the comparison window: also compare_date_range_reversed and compare_date_range_too_large.
500stats_unavailableThe rates couldn’t be loaded. Retry.
503stats_too_slowThe window holds too many messages to count inside the request. Ask for fewer days.

#Check a message for carrier blocks

Tells you if carriers or Ready’s content filter would block a text before you send it. It runs the same content check a real send gets: our keyword rules, then an AI review against US carrier policy. Nothing is sent and nothing is charged. You can run 30 checks an hour per account, shared by your team. Needs the send_sms permission.

Parameters

bodystringRequired
The message to check, up to 2000 characters. Merge fields like {{contact.first_name}} are fine.
Returns
blockedboolean
true when this text would be blocked. At the top level.
reasonstring | null
Why it would be blocked, in plain words. null when it passes.
uncheckedboolean
true when the check didn’t finish. With blocked false, the keyword rules passed but the AI review gave no answer, so the text was only partly checked. With blocked true, the content review couldn’t run at all, so this says nothing about the text; a send right now would be held too. Either way, try again later for a full answer.
Errors
HTTPCodeWhen
400invalid_fieldbody is an object or a list.
400no codebody is blank or longer than 2000 characters.
429no codeYour account ran 30 checks in the last hour. Wait and try again.
500no codeThe check didn’t run. Retry in a minute.