readyDocs Open the appOpen the app

Campaigns

A campaign texts one message to an audience: all your contacts, a pipeline stage, a smart list, filter rules or hand-picked contacts. Create it, check the price with quote, then send it, or pass scheduled_at when you create it and we send it at that time. For a series of texts spaced out over days, see Drip campaigns. Test keys can only read campaigns: every POST, PUT, PATCH and DELETE under /campaigns answers 403 test_key_not_allowed, including the ones that only price or count. A campaign id that isn’t a whole number answers 400 (invalid_field, or INVALID_ID on the results and AI routes), and a number that isn’t a campaign on your account answers 404 CAMPAIGN_NOT_FOUND.

#The Campaign object

List campaigns and Get a campaign return these, with live delivery counts. The fields below are the ones most callers need; the object carries every saved setting of the campaign too.

Create, update, pause, resume, unschedule and stop return the campaign as it is saved, which is not quite the same shape: it has total_replies in place of replies, its total_sent is the last saved count rather than the live one, and it has no dlr_delivered, dlr_failed, dlr_pending, delivery_rate, skipped, was_stopped, waiting_note or can_add_recipients. Call Get a campaign after a change to read the full shape.

idinteger
The campaign id.
namestring
Shown in your campaign list.
statusenum
Where the campaign is.
Possible values

draft, scheduled, sending, paused, completed, failed and cancelled. A stopped campaign is completed with fewer sent than its audience.

message_bodystring
The text, with merge fields unfilled.
total_recipientsinteger
Audience size.
total_sentinteger
Texts sent so far.
dlr_deliveredstring | null
Carrier delivery count: delivered. null when we couldn’t read the counts; the answer then has unavailable: ["delivery_counts"].
dlr_failedstring | null
Carrier delivery count: failed. null the same way as dlr_delivered.
dlr_pendingstring | null
Carrier delivery count: pending. null the same way as dlr_delivered.
delivery_ratenumber | null
Percent delivered out of delivered plus failed, like 98.2. null until a text resolves.
repliesinteger
Replies to this campaign. The same number as total_replies, which the object also carries.
skippedinteger
Recipients the send left out, such as people in quiet hours or opted out.
was_stoppedboolean
true when you stopped it, or its stop_at passed.
waiting_notestring | null
Only on a scheduled campaign past its scheduled_at that has not started: why it is still waiting (outside its sending hours, or still waiting to start 15 minutes on). null otherwise.
can_add_recipientsboolean
true when Add recipients would take more people for it now.
scheduled_atISO 8601 string | null
When it’s scheduled to send, or null.
created_atISO 8601 string
When it was created.

#List campaigns

Every campaign on your account, newest first, with live delivery counts. Not paginated. Returns a list of Campaign objects in data. When we couldn’t read the delivery counts, the dlr_* fields are null and the answer has unavailable: ["delivery_counts"].

Parameters

lightboolean · query
Send light=1 for just id and name of each campaign, in the same order, without delivery counts. Much faster. Any value counts, so leave the parameter out to get the full list.
scheduledboolean · query
Send scheduled=1 for only the scheduled campaigns, each with id, name, status, scheduled_at, created_at and total_recipients. Any value counts. light wins when you send both.

#Get a campaign

One campaign, the same Campaign object List campaigns returns for it, with live delivery counts. Use it to follow one campaign while it sends. When we couldn’t read the delivery counts, the dlr_* fields are null and the answer has unavailable: ["delivery_counts"].

Errors
HTTPCodeWhen
400invalid_fieldThe id isn’t a whole number.
404CAMPAIGN_NOT_FOUNDNo campaign with that id on your account, or you deleted it.
500CAMPAIGN_LOAD_FAILEDWe couldn’t load it. Try again.

#Create a campaign

Saves a draft, or a scheduled campaign when you pass scheduled_at. Creating doesn’t send; a draft goes out when you call send, and a scheduled campaign is charged when it sends. Needs the send_sms permission. Returns 201 and the campaign as saved (the saved shape), with status draft or scheduled. A create with no name, message or audience hands back your newest blank draft with reused: true instead of making another.

Retrying a timed-out create is safe for a blank draft only. For anything else, list your campaigns first so a scheduled one doesn’t go out twice.

Parameters

namestringRequired
Shown in your campaign list.
More parameters
message_bodystring
Required to schedule. Merge fields like {{contact.first_name}} are filled per contact. Your compliance footer is added the way your settings say.
recipient_filterstringDefault all
all for every active contact, a pipeline stage id for that stage, rules to use recipient_rules, or manual to use recipient_contact_ids.
segment_idinteger
Send to a smart list. segment_id is a smart list id, not a text segment.
recipient_rulesobject[]
Filter rules, the same shape a smart list uses. Use with recipient_filter: "rules". Rules that match nobody send to nobody, never everyone.
recipient_contact_idsinteger[]
Hand-picked contact ids, up to 100,000.
scheduled_atstring
When to send. With Z or an offset it’s that exact time; without one it’s read in timezone, else your account’s time zone. Must be at least 1 minute from now, or it is refused with a 400.
timezonestring
IANA name such as America/Chicago.
phone_number_idinteger
Send from this number. Leave it out to use your default.
phone_number_idsinteger[]
Send from two or more of your active numbers, taking turns as from_strategy says. Ignored when you pass phone_number_id. One number here is not kept; use phone_number_id for one.
from_strategyenumDefault local
How we pick among your numbers when phone_number_id isn’t set. local picks the number that has sent the least among those in the recipient’s area code when local presence is on, and your default number when it is off. even (take turns), random and deliverability (the number that has sent the least) spread across all your numbers either way; with local presence on, deliverability picks the same number as local. Any other value is read as local.
smart_send_enabledboolean
true texts each contact at the hour they usually reply, inside your delivery window, so the send goes out over time. Price it with quote and the same flag.
quiet_hours_modeenumDefault defer
A recipient in their quiet hours: defer holds the text and sends it when their quiet hours end, if that is within 24 hours of the send starting and before any deadline in the message; otherwise they are skipped. skip leaves them out.
media_urlstring
An http(s) link to an image or PDF. Sends as MMS.
delivery_window_startstring
Send only between this time and delivery_window_end, like 09:00.
delivery_window_endstring
The end of that window, like 17:00.
delivery_window_daysstring
Days to send on, as lowercase day names separated by commas, like mon,tue,wed. Use sun, mon, tue, wed, thu, fri or sat.
send_rate_per_secinteger
Texts per second. Capped at your account’s limit (GET /campaigns/send-cap).
drip_interval_secinteger
Seconds between each text, for a slow send. 7 is about 500 an hour.
stop_atstring
Stop sending at this time. A follow-up that comes due after it is not sent.
stop_after_messagesinteger
Stop after this many sent: a whole number above 0. Leave it out, or send null or 0, for no limit; any other value is refused with stop_rule_invalid.
stop_after_failuresinteger
Stop after this many failed: a whole number above 0. Leave it out, or send null or 0, for no limit; any other value is refused with stop_rule_invalid.
stop_combinatorenumDefault any
Ignored: the first stop rule reached (stop_at, stop_after_messages or stop_after_failures) ends the send.
is_ab_testboolean
true makes it an A/B test: message_body is version A, message_variants holds the others.
message_variantsstring[]
The other versions of the text in an A/B test. Ignored unless is_ab_test is true.
ab_splitintegerDefault 50
The percent that gets A when there are two versions.
tag_on_sendstring
Tag every contact who gets the text.
reply_keywordstring
A word people reply with, like YES. We keep letters and digits only, upper case, up to 20. An automation that runs on replies to a campaign matches a reply containing it to this campaign, even if the contact got a newer one since.
followup_enabledboolean
true sends one follow-up text to people who didn’t reply after followup_delay_hours. It is not sent after stop_at or a Stop, and it skips anyone whose merge field in the follow-up is blank. The follow-up is charged like any campaign text, for each person it goes to, from your balance. If the campaign has a picture, the follow-up sends it again and is charged as a picture message. Needs followup_message: without one the follow-up is saved off. Kept for existing integrations: for a new follow-up, add a step to a drip campaign, which is how the dashboard builds one.
followup_messagestring
The follow-up text. Required when followup_enabled is true.
followup_delay_hoursinteger
Hours without a reply before the follow-up goes out.

With scheduled_at, a create can answer 200 with success: false and a needs_confirmation field (empty_merge_field or duplicate_campaign) instead of saving. Nothing was saved. Read message, then repeat the same request with one of the answers Send takes: confirm_empty_merge, skip_empty_merge, confirm_duplicate or duplicate_exclude_blast_id.

Errors
HTTPCodeWhen
400campaign_name_requiredNo name.
400consent_requiredYou passed scheduled_at without consent_attested: true.
400message_requiredNo message on a scheduled or send-now campaign.
400message_length_invalidThe message is over 25 segments, or over 1,600 characters with media_url.
400media_url_invalidmedia_url isn’t an http(s) link.
400variants_invalidmessage_variants isn’t a list of texts.
400ab_split_invalidab_split isn’t a whole number from 1 to 99.
400timezone_unknowntimezone isn’t an IANA name we know, like America/Chicago.
400delivery_window_invalidThe delivery window ends before it starts.
400invalid_fieldA field has the wrong type or format, such as a window time that isn’t 24-hour HH:MM or a quiet_hours_mode that isn’t defer or skip. error says which.
400AUDIENCE_TOO_LARGEWith scheduled_at, the audience is over 500,000 recipients, the most one campaign sends to. Nothing was saved. Narrow it with a smart list or filter, or split it into smaller campaigns.
400schedule_too_soonscheduled_at is in the past or too close to now (under the minimum lead time).
400stop_rule_invalidstop_at isn’t a date and time, is less than a minute away, or is more than a year away, or stop_after_messages / stop_after_failures is not a whole number above 0.
400pace_rate_invalidsend_rate_per_sec isn’t a whole number of at least 1.
400pace_sequence_invaliddrip_interval_sec isn’t a whole number of at least 2.
400pace_field_unknownThe body has interval_sec, per_second, per_minute, per_hour or per_day. Those are for PATCH /campaigns/:id/pace; here, send drip_interval_sec or send_rate_per_sec.
400TOO_MANY_MANUAL_RECIPIENTSMore than 100,000 hand-picked contacts.
400NOT_ASSIGNED_TO_YOUYour seat only reaches its assigned contacts, and every hand-picked contact belongs to someone else.
400SEGMENT_NOT_FOUNDThe smart list doesn’t exist.
400SEGMENT_RULES_INVALIDThe smart list’s saved rules are damaged.
400NO_RECIPIENTSNobody in the audience can be texted. Only for a scheduled campaign; a draft can be empty.
400no_recipientsYou answered duplicate_exclude_blast_id and everyone was in that other campaign.
400number_not_activephone_number_id isn’t an active number on your account.
400sender_number_inactiveA number in phone_number_ids isn’t an active number on your account.
400CONTENT_BLOCKEDThe message breaks our content rules. Nothing was saved. error names the word that matched when it can, and matched lists what matched. message is a fixed sentence.
402BILLING_PAST_DUEYour last payment failed. Update your card in Billing.
403number_not_assignedYour seat sends only from its assigned numbers, and you picked another one.
409assigned_number_requiredWith scheduled_at: your seat sends only from its assigned numbers, so pick one with phone_number_id.
500CAMPAIGN_CREATE_UNCONFIRMEDSomething broke while we saved it. List your campaigns before you try again.
503CONTENT_REVIEW_UNAVAILABLEWe couldn’t finish checking the message, so nothing was saved. Try again shortly.

#Update a campaign

Changes a draft, scheduled or paused campaign. Send only the fields you want to change; they take the same values as create. Needs the send_sms permission. Returns the campaign as saved (the saved shape). Like create, a save that schedules can answer 200 with needs_confirmation instead; nothing was saved, so repeat it with your answer.

Parameters

Errors

Besides these, an update answers the same 400 field errors as create.

HTTPCodeWhen
400NOT_EDITABLEIt has started sending or finished. Duplicate it to send a new version.
400PARTIALLY_SENT_LOCKEDIt already went out to part of its audience, so its message, attachment and recipients can’t change.
400CHARGED_AUDIENCE_LOCKEDIt was already charged for its recipients, so its message and recipients can’t change.
400CHARGED_MEDIA_LOCKEDIt was already charged, so its attachment can’t change.
400AUDIENCE_TOO_LARGEThe save schedules the campaign, or changes a scheduled one’s audience, and the audience is over 500,000 recipients. Nothing was saved.
403attestation_requiredThe save needs a new consent record (see consent_attested) and you didn’t pass consent_attested: true. Nothing was saved.
404CAMPAIGN_NOT_FOUNDNo campaign with that id on your account.
409state_changedIts status changed while we saved. Nothing was saved.
409ALREADY_SENTIt already texted some people, so it can’t go back to a draft. Nothing was saved.

#Delete a campaign

A draft, scheduled or failed campaign that never texted anyone is deleted for good. Any other finished campaign (completed, cancelled, or failed after texting people) is taken off your list instead: List campaigns and Get a campaign stop showing it, and its texts stay in your history. If an unsent campaign still holds money we charged for it, we put that back on your balance before deleting it. Stop a sending or paused campaign first. Needs the send_sms permission. Returns {"success": true}.

Errors
HTTPCodeWhen
400invalid_fieldThe id isn’t a whole number.
404CAMPAIGN_NOT_FOUNDNo campaign with that id on your account, or it’s already deleted.
409not_deletableIt’s sending or paused. Stop it, then delete it. status says which.
409refund_pendingWe couldn’t put its held charge back yet, so it wasn’t deleted. It won’t send. Try again in a minute.
500DELETE_FAILEDThe delete didn’t go through. Check the campaign, then try again.

#Duplicate a campaign

Copies a campaign into a new draft named Copy of plus its name. The copy keeps the message, audience, numbers, pace, stop rules and other settings; its counts, schedule and consent record start fresh, so you confirm consent again when you send it. Free; nothing is sent. Send an Idempotency-Key header to retry without making a second copy (Idempotency). Needs the send_sms permission. Returns the new draft as saved (the saved shape) in data.

Errors
HTTPCodeWhen
400invalid_fieldThe id isn’t a whole number.
404CAMPAIGN_NOT_FOUNDNo campaign with that id on your account.
500CAMPAIGN_LOAD_FAILEDWe couldn’t load it, so no copy was made.
500DUPLICATE_FAILEDNo copy was made. Try again.

#Price a campaign

What a send would cost, priced the way we charge it: every recipient’s text with merge fields filled and your footer on. A follow-up (followup_enabled) is not in this price; it is charged when it goes out. Nothing is saved or sent. Needs the send_sms permission.

Parameters

Takes the same audience parameters as Create a campaign: recipient_filter, segment_id, recipient_rules and recipient_contact_ids.

messagesobject[]Required
1 to 20 messages, each {"body": "..."}, or {"media": true} for MMS. A message can also carry variants (extra versions of the text) and ab_split (the share that gets the first version), which change the price the same way they change the send.
scheduledboolean
Price it as a scheduled send. A scheduled send can add the compliance surcharge, a per-segment charge for number validation and TCPA scrubbing, each when it’s on (see Billing; your per-segment price is compliance_usd_per_segment in Get your balance).
smart_send_enabledboolean
Price it with smart send on.
drip_interval_secinteger
Seconds between each text, for a slow send.
delivery_window_startstring
Send only inside a daily window that starts at this time. When both ends are set, the send goes through the queue and can add the compliance surcharge.
delivery_window_endstring
The end of that daily window.
Returns
data.recipientsinteger
How many people it would go to.
data.messages[]object[]
media, segments and charge_cents for each message. segments is the total over every recipient, or null for a picture message, which is priced per recipient.
data.charge_centsinteger
The message charges, in cents, before the compliance surcharge. When screening_per_unit_usd is above 0, the price is this plus billable_units times screening_per_unit_usd (in dollars).
data.billable_unitsinteger
The units it would bill, added up over all messages: segments for a text, one per recipient for a picture message.
data.screening_per_unit_usdnumber
The compliance surcharge per segment when this send owes it (number validation or TCPA scrubbing on, and the send goes out over time), else 0.
Errors
HTTPCodeWhen
400QUOTE_BAD_MESSAGESNo messages, more than 20, or a text (or one of its variants) over 25 segments. error says which.
500QUOTE_UNAVAILABLEWe couldn’t price it right now. Try again.

#Preview an audience

Who an audience would reach before you create or send a campaign: the active, textable contacts it matches, by the same rules the send uses. Nothing is saved or sent, and it’s free. Needs the send_sms permission.

Parameters

Takes the same audience parameters as Create a campaign: recipient_filter, segment_id, recipient_rules and recipient_contact_ids.

count_onlyboolean
true answers only total and capped, without phone numbers. The fastest way to count.
limitinteger
Only the newest this many matching contacts, up to 50,000.
include_idsboolean
true adds the matching contact ids as ids.
include_sampleboolean
true adds sample: up to 200 contacts, each with name, phone and stage.
sample_offsetinteger
With include_sample, the page of 200 that starts here, 0 to 50,000. The answer adds sample_total and sample_next_offset (null on the last page).
Returns
data.totalinteger
How many it reaches. Past 50,000 this is a close estimate.
data.phonesstring[]
Their numbers, up to 50,000. Left out with count_only, on a sample page, and for a teammate when Teammates can export contacts is off: then export_restricted is true and ids is left out too.
data.cappedboolean
true when you passed limit.
data.walledboolean
Set when your seat only sees its assigned contacts: total counts everyone the send reaches, and the lists hold only the contacts you can see.
Errors
HTTPCodeWhen
400SEGMENT_NOT_FOUNDThe smart list doesn’t exist.
400SEGMENT_RULES_INVALIDThe smart list’s saved rules are damaged.
400invalid_fieldcount_only isn’t a boolean, or sample_offset is out of range.
500RECIPIENT_PREVIEW_FAILEDWe couldn’t count the audience. Try again.