ready. Try Ready →Sign up

#Opt-outs and scrubbing

Ready blocks opted-out numbers for you: it refuses sends to anyone in GET /v1/opt-outs, so you don't need to check before every send.

Details

Every opt-out (STOP, marked opted out, Do Not Disturb for texts) answers 403 opt_out_locked, with reason saying why (for example sms_stop_keyword or tcpa_scrub). A refused send isn't charged. It shows in GET /v1/messages as a failed row with cost_usd: 0.

How people opt out

An inbound STOP or similar keyword opts the contact out immediately. Texting START opts them back in, unless the lock came from TCPA scrubbing or your CRM.

Details

Keywords include STOP, STOPALL, UNSUBSCRIBE, CANCEL, END, QUIT, REVOKE, OPT OUT and similar, plus clear phrases like "stop texting me" and any custom keywords saved on your account earlier.

Only a keyword opt-out fires the contact_opted_out webhook (your own dnd_sms updates don't), so reconcile with GET /v1/opt-outs.

Opting someone out yourself

To record an opt-out from a call, an email or a web form, send PUT /contacts/:id with {"dnd_sms": 1}. This can't be undone over the API.

Details

Trying answers 403 opted_out, or 403 opt_out_locked if the contact also replied STOP. Only the contact texting START lifts it.

TCPA litigator scrubbing

Ready scrubs contacts automatically when they're created or imported and before sends. A match locks the contact like a STOP, and START doesn't lift it.

Details

The lock sets opt_out_locked=1, status=opted_out, opt_out_source="tcpa_scrub" and is_litigator=1, and records the matched lists in tcpa_status (comma-separated). Choose the lists with scrub categories.

Scrubbing costs your scrub rate per segment when it's on: $0.005 by default. Your rate is scrub_per_segment_usd in GET /compliance/scrub-categories.

Suppression fields on a contact

Every contact from /contacts carries these. Flags are integers, 0 or 1, not booleans.

Details
FieldMeaningBlocks texts?
opt_out_lockedSet by a STOP or a TCPA lock. Cleared only by START.Yes
statusopted_out when opted out.Yes, when opted_out
dnd_smsDo not text.Yes
dnd_email, dnd_callsDo not email, do not call.No
opt_out_sourcesms_stop_keyword, profanity_opt_out, tcpa_scrub, ghl_dnd, ai_intent, intent_scan, cross_family_stop_propagation, or null for a manual DND.
opt_out_atWhen they opted out (ISO 8601). This is the real opt-out time.
is_litigatorMatched a TCPA litigator list.Yes, through the lock
tcpa_statusThe matched lists, comma-separated, or null.
sms_invalidThe number can't receive texts.Depends on an account setting

Which list to use

Sync from GET /v1/opt-outs: it is exactly who Ready won't text. The CSV exports are for audits.

Details
ListWhat's in itReady blocks texts to all of it?Format
GET /v1/opt-outsEveryone Ready won't text: STOP, opted out, DND for texts, TCPA lock.YesJSON, cursor pages
?compliance=optoutThe same people, minus contacts marked spam (unless include_spam=1).YesCSV, 6 an hour
?compliance=dncDo not contact on any channel (see compliance).No: email-only and call-only DND contacts can still get textsCSV, 6 an hour

Keeping your own copy in sync

Walk GET /v1/opt-outs?limit=500 until has_more is false, listen for contact_opted_out, and walk the list again on a schedule for the other opt-outs. Keep your own copy: once a contact is deleted, the API reports its number as not opted out.

Details

Deleting a contact removes it from /v1/opt-outs. After a delete, a re-created contact and GET /v1/opt-outs/:phone show the number as not opted out. Ready keeps the opt-out in a separate record for its own send check, so sends to the number should still be refused, but don't rely on the API to tell you: check your copy before you add a number back.

#Check one number

GET/v1/opt-outs/:phone

Whether a number is opted out on your account.

  • Cost Free

Path parameters

phonestringRequired
US or Canada (+1) only, E.164 or 10 digits.
DetailsURL-encode the + as %2B if your client needs it. Other countries aren't supported: only the last 10 digits are used, so the answer is for a different number.

Returns

data.phoneE.164 string
The number checked.
data.opted_outboolean
true if the number replied STOP, was marked opted out, or is Do Not Disturb for texts.
data.sinceISO 8601 string | null
When the number opted out (the contact's opt_out_at). null when not opted out, or when Ready has no opt-out time.

Errors

HTTPCodeWhen
400invalid_numberFewer than 10 digits, or not a phone number.
500internal_errorCheck failed. Don't text the number; retry.

#List opt-outs

GET/v1/opt-outs

Every number your account must not text, by contact, newest contact first.

  • Cost Free

Query parameters

limitintegerOptionalDefault 100Max 500
Page size.
before_idintegerOptional
The next_before_id from the previous page. offset isn't supported and is ignored.

Returns

data[]object[]
phone and since (as in Check one number). Sorted by contact, not by opt-out date.
has_moreboolean
Loop until it's false. Don't loop on next_before_id: it's set on the last page too.
next_before_idinteger
Pass as before_id.

Errors

HTTPCodeWhen
500internal_errorRead failed. Don't treat the list as empty.

#Scrub numbers

POST/v1/scrub

Checks up to 1,000 US numbers against the TCPA litigator and do-not-call lists your account screens.

  • Auth API key with edit_contacts
  • Idempotent Yes, with Idempotency-Key
  • Rate limit About 120 requests/min per account
  • Cost Your scrub rate per valid number
Important. Each valid, unique number is billed at your scrub rate.
DetailsIt is the same rate as a send's scrub (scrub_per_segment_usd, $0.005 by default). A number your account scrubbed recently can cost less. Invalid numbers aren't charged.

Headers

Idempotency-KeystringRecommended
Retry safely without paying twice.

Body parameters

phonesstring[]Required
1 to 1,000 US numbers, E.164 or 10 digits.

Returns

data.totalinteger
Numbers in the request.
data.results[]object[]
One per number: phone, normalized, valid, litigator, dnc_complainer, federal_dnc, state_dnc, blocked, cached, scrubbed_at.
DetailsA list flag of null means your account doesn't screen that list. On a problem, error_code and error: invalid_number (not charged), scrub_failed, or scrub_unavailable (not checked, not charged).

It creates no contacts. A number that's already one of your contacts gets the new result written onto it.

Errors

HTTPCodeWhen
400no codephones is missing, empty or not a list.
400too_many_numbersMore than 1,000 numbers.
402insufficient_balanceThe batch could cost more than your balance (required_cents, balance_cents).
403permission_deniedThe key's user lacks edit_contacts.
403test_key_not_allowedCalled with a test key.
409scrub_offScrubbing is off.
503tcpa_not_configuredScrubbing isn't available right now.

#Scrub one number

GET/v1/scrub/:phone

The same check for one number. This GET is billed, like one number in Scrub numbers.

  • Auth API key with edit_contacts
  • Rate limit About 120 requests/min per account
  • Cost Your scrub rate

Path parameters

phonestringRequired
A US number, E.164 or 10 digits.

Returns

dataobject
One result, shaped like an item of results[] above.

Errors

HTTPCodeWhen
402insufficient_balanceBalance too low.
403permission_deniedThe key's user lacks edit_contacts.
403test_key_not_allowedTest keys can't make this billed read.
409scrub_offScrubbing is off.
503tcpa_not_configuredScrubbing isn't available right now.

#Get scrub categories

GET/compliance/scrub-categories

Which lists your account screens contacts against, and your scrub rate.

  • Cost Free

Returns

Fields are at the top level of data.

master_enforceboolean
Whether scrubbing is on at all.
categories[]object[]
key (tcpa, dnc_complainers, federal_dnc, state_dnc), label, enabled, effective, default_on, source, attestation_required_to_disable.
required_phrasestring
I UNDERSTAND: what confirmation_text must say to turn off a protected list.
scrub_per_segment_usdnumber
Your scrub rate. The response has a few more billing fields used by the dashboard.

Errors

HTTPCodeWhen
500internal_errorRead failed (the code is in error).

#Change scrub categories

POST/compliance/scrub-categories

Turns screening lists on or off. Account owner only.

  • Cost Free
Important. Errors here put the machine code in error and the sentence in message (except invalid_field and OWNER_ONLY, below).

Body parameters

categoriesobjectRequired
Any of tcpa and dnc_complainers (on by default), federal_dnc and state_dnc (off by default), each a JSON boolean.
DetailsSend real true and false: only true, 1, "1" and "true" mean on, so "yes" or "on" silently mean off. Turning every list off turns scrubbing off.
confirmation_textstringTo turn off tcpa or dnc_complainers
Exactly I UNDERSTAND.

Returns

dataobject
master_enforce and the saved categories.

Errors

HTTPCodeWhen
400categories_requiredcategories is missing or not an object.
400unknown_categoryA key isn't one of the four.
400attestation_requiredTurning off tcpa or dnc_complainers without confirmation_text (disabling lists them).
400invalid_fieldconfirmation_text is a list or object. Usual shape: code, error, field.
403OWNER_ONLYCaller isn't the account owner (code in code).
403test_key_not_allowedCalled with a test key.

#Export opt-outs as CSV

GET/contacts/export

A CSV of opted-out contacts, for audits and list hygiene.

  • Rate limit 6 exports/hour per account, shared with the dashboard (RateLimit-Remaining header)
  • Cost Free

Query parameters

complianceenumRequired
Must be exactly optout or dnc. Any other value exports all your contacts.
Details
  • optout everyone Ready won't text: replied STOP, DND for texts, or locked by TCPA scrubbing. opt_out_source says which. File opted-out-contacts.csv.
  • dnc do not contact on any channel: the above plus email-only and call-only DND, litigators and invalid numbers. Not the federal Do-Not-Call registry. File dnc-list.csv.
include_spamintegerOptional
Contacts marked spam are left out unless you send 1. Add it for an audit-complete file.

The file starts with a UTF-8 byte-order mark (BOM): decode it as utf-8-sig in Python.

Details

Otherwise the first column name reads \ufeffid.

API keys can always export.

Errors

HTTPCodeWhen
403EXPORT_DISABLEDA teammate, on an account where only the owner can export contacts.
429rate_limitedMore than 6 exports in an hour (in code).