readyDocs Open the appOpen the app

Drip campaigns

A drip campaign sends a series of texts, each a set time after the last. A contact you add gets step 1 after its delay, then step 2, and so on. Anyone who opts out isn’t texted again. A drip can also branch, wait for a reply, tag a contact or call a webhook: create one with any of the step types here or build it in the app, then add contacts from your system. Sending hours, Smart Scheduling, the sending number and the reports are on Drip campaign settings. Test keys can’t call the endpoints that change a drip (403 test_key_not_allowed), and an id that isn’t a drip campaign on your account answers 404 DRIP_NOT_FOUND. Some fields and codes still say sequence, our older name for a drip campaign: sequence_id is a drip campaign id.

A drip has no pause, resume or stop endpoints like a campaign. One call does all three, Set a drip’s status: pause is paused, resume is active and stop is completed (it stops all sending, not a sign every contact finished; set it active again and contacts carry on from their step). It answers a bare {"success": true}, and a missing drip is DRIP_NOT_FOUND where a missing campaign is CAMPAIGN_NOT_FOUND.

#The Drip campaign object

List drip campaigns returns these. Create a drip campaign returns one.

idinteger
The drip campaign id.
namestring
The drip’s name.
statusenum
active, paused, completed or draft. Only an active drip sends.
steps[]object[]
In order: step_order, step_type, name, message_body, delay_days, delay_hours, delay_minutes, and the branch and wait fields in Drip steps. Without light, each step also has leads_at_step: contacts active or waiting on a reply whose next step is this one.
total_enrolledinteger
Contacts in it now, finished ones included. Contacts you removed are not counted.
timezonestring or null
The zone its days, sending hours and start_at times use. A new drip starts in your account’s time zone. null (your account had no time zone when the drip was made, and you have not set sending hours with a zone) means America/New_York.
delivery_window_start, delivery_window_end, delivery_window_daysstring or null
Its sending hours, from Set a drip’s sending hours. null is no limit beyond quiet hours.
smart_send_enabledinteger
1 when Smart Scheduling is on, else 0.
phone_number_id, from_phone_idsinteger, string or null
The sending number, or the numbers it takes turns between. Both null means your account picks the number.

Without light, List drip campaigns adds live counts to each drip:

enrolled_countinteger
Contacts still going through it: active or waiting on a reply.
paused_count, completed_count, stopped_countinteger
Contacts paused, finished, or stopped. A low balance pauses contacts of an active drip; they start again on their own, keeping their spacing, once you top up and your balance is at least $1. An opt-out or a removal stops them.
queued_now, past_dueinteger
Active contacts whose next step is due now. past_due counts only those with a send time that has passed.
queued_today, queued_tomorrow, queued_7dinteger
Active contacts due later today, tomorrow (days in the drip’s time zone), and in the next 7 days.
schedule_14dobject[]
The next 14 days, one {date, count} per day that has sends. List who is scheduled on a day lists the contacts.

#Drip steps

Every step has a step_type (leave it out for send_sms), an optional name (up to 80 characters) and a wait before it: delay_days, delay_hours, delay_minutes, each from 0 up to 10 years. A day keeps the same clock time in the drip’s time zone, so a 9am text with a 1 day wait goes out at 9am the next day even across a daylight saving change. The first step after you add a contact is the exception: its wait is counted in plain hours from when you added them. message_body holds what the step needs, which depends on its type. A step’s place in the list is its step_order, starting at 1, and branch fields point at a step_order. After any step other than if_else, wait_reply and end, the contact moves to the next step_order; when there is none, the drip is finished for them. The step types and their fields are in Step types at the end of this page.

#List drip campaigns

Every drip campaign on your account, newest first. To pick a drip or read its steps, send light=1: it is fast and returns steps only. Without it, each drip also carries live counts, which can be slow on large accounts. Returns a list of Drip campaign objects in data.

Parameters

idinteger, query
Just this drip, with its counts. The list then holds one drip, or none when it isn’t yours. Ignored with light.
lightboolean, query
Send light=1 for just the drips and their steps, without counts. Much faster on large accounts. Any value counts, so leave the parameter out to get the counts.
Errors
HTTPCodeWhen
400invalid_fieldid isn’t a whole number from 1 up.
500SEQUENCE_LIST_FAILEDWe couldn’t read them. Try again.

#Create a drip campaign

Creates a drip campaign and its steps. It’s on as soon as it’s saved unless you pass status, but sends nothing until you add contacts. Each text is charged when it sends: its segments times your per-segment rate plus the carrier fee, and the compliance surcharge when your account owes it (Get balance and rates has each price). Needs the send_sms permission. Returns 201 and a Drip campaign object with its steps.

Parameters

namestringRequired
The drip’s name, up to 120 characters. A second create with the same name inside 10 seconds creates nothing: it answers 200 (not 201) with deduped: true and only the first drip’s id and name in data, so a retry is safe.
stepsobject[]
In order, up to 200. Any of the step types, with their branch and wait fields; a step’s place sets its step_order, so branch fields point at positions counting from 1. Create refuses an unknown type or a loop. It does not check that a branch points at a step that exists; Save a branched drip does. You can leave steps out and add them later with Replace a drip’s steps.
statusenumDefault active
active, paused or draft.
smart_send_enabledboolean
Moves each step after the first toward the hour the contact usually replies, up to 12 hours later.
Returns
dataobject
The drip campaign with its steps.
empty_mergeobject
Merge fields that are blank for some of your contacts, by step. Those contacts skip that step unless it has allow_empty_merge: true.
Errors
HTTPCodeWhen
400no codeNo name.
400invalid_fieldA name over 120 characters, or more than 200 steps.
400DRIP_STATUS_INVALIDstatus isn’t one of those.
400BAD_SHAPEsmart_send_enabled isn’t on or off.
400CONTENT_BLOCKEDA step breaks our content rules. step_index says which. The other step refusals are in Drip steps.
402BILLING_PAST_DUEYour last payment failed. Update your card in Billing. FRAUD_HOLD while your account is under a security review.
409DRIP_NAME_JUST_USEDA drip with this name but different steps was created seconds ago.
500DRIP_CREATE_FAILEDNothing was saved. Try again.
500DRIP_CREATE_PARTIALIt may have saved. List your drips before you try again.

#Draft a drip with AI

Turns a plain description into a proposed drip, or into up to 3 questions to answer first. Nothing is saved: pass the steps you keep to Create a drip campaign. Needs the send_sms permission.

Parameters

descriptionstringRequired
What the drip should do, up to 4,000 characters.
force_buildboolean
true always drafts the steps. Leave it out and the answer may be up to 3 questions to answer first instead of a draft.
Returns
dataobject
Either questions (each a question and its options), or a draft: name, summary, trigger, branched and steps in the step shape.
Errors
HTTPCodeWhen
400DESCRIPTION_REQUIREDNo description.
400DESCRIPTION_TOO_LONGOver 4,000 characters.
422no codeIt produced no steps, or won’t write this drip. error says why.
502SEQUENCE_AI_BAD_RESPONSEThe draft came back unreadable. Rephrase and try again.
503SEQUENCE_AI_UNAVAILABLEThe AI builder is off. Also a 503 with no code when it has reached its limit for the month.

#Replace a drip’s steps

Replaces every step of a drip with the ones you send, in order: a step’s place sets its step_order. While contacts are partway through it (active, waiting on a reply or paused), only the wording can change: the same steps, types, waits and branches, with a new name or message_body. Anything else is refused, so nobody is sent down the wrong path. Send each step whole: a field you leave out is cleared, except step_type and media_url, which keep what is stored at that position. Needs the send_sms permission. Returns the saved steps in data, and empty_merge as on create. The steps it replaces are kept as an earlier version: list them with List earlier versions (kind=drip) and put one back with Restore an earlier version.

Parameters

idinteger, pathRequired
The drip campaign id.
stepsobject[]Required
From 1 to 200 steps.
Errors
HTTPCodeWhen
400invalid_fieldNo steps, or more than 200.
400CONTENT_BLOCKEDA step breaks our content rules. The other step refusals are in Drip steps.
409DRIP_HAS_ACTIVE_CONTACTSContacts are partway through it and this changes more than wording. Nothing was changed.
500DRIP_STEPS_SAVE_FAILEDNothing was saved; the old steps are still in place. Try again.

#Save a branched drip

Replaces every step of a drip, keeping the step_order you give each one, so your branch fields stay right. Use it for drips with if_else or wait_reply steps. The same rules apply as Replace a drip’s steps, the same errors, and also 400 DRIP_GRAPH_INVALID when a step_order is missing, under 1 or used twice, or a branch points at a step that isn’t in the list. A blank text step is refused wherever it is. Needs the send_sms permission.

Parameters

idinteger, pathRequired
The drip campaign id.
stepsobject[]Required
From 1 to 200 steps, each with its own step_order. End each branch with an end step.

#Rename a step

Changes one step’s name and nothing else, even while contacts are partway through. Needs the send_sms permission. Returns {"success": true, "name": "..."} with the name as saved.

Parameters

idinteger, pathRequired
The drip campaign id.
orderinteger, pathRequired
The step’s step_order.
namestring
The new name. Longer than 80 characters is cut to 80.
Errors
HTTPCodeWhen
404DRIP_STEP_NOT_FOUNDNo step at that order. A 400 with this code when order isn’t a number.

#Add contacts to a drip

Adds contacts to a drip campaign. Each gets step 1 after its delay, inside the drip’s sending window. Each text is charged when it sends: its segments times your per-segment rate plus the carrier fee, and the compliance surcharge when your account owes it (Get balance and rates has each price). A drip that isn’t active holds their texts until you turn it on. Contacts already in it are skipped and counted. Contacts you pick by id who have opted out or can’t be texted are skipped and counted too; a smart list or filter only matches contacts who can be texted, so the others are left out and not counted. If every contact you picked by id is opted out or not textable, nobody is added and you get NOT_TEXTABLE. Needs the send_sms permission.

Parameters

idinteger, pathRequired
The drip campaign id.
More parameters
contact_idsinteger[]
The contacts to add.
attestation_idinteger
Instead of consent_attested: a consent confirmation you already recorded for exactly these phone numbers. Each one is used once.
segment_idinteger
Other way to pick the audience: everyone in a smart list.
filter_rulesobject[]
Other way to pick the audience: everyone matching filter rules.
recipient_filterstring
Other way to pick the audience, the shape a campaign takes. It is the same as the fields above: manual with recipient_contact_ids (same as contact_ids), rules with recipient_rules (same as filter_rules), all for every active contact who can be texted, or a pipeline stage id. contact_ids wins when any of them are yours.
start_atstring
Hold the first text until this time. A date and time like 2027-01-05T09:00 is read in the drip’s time zone; a drip with none (made when your account had no time zone, until you set one) uses America/New_York. A time ending in Z or an offset like -08:00 is exact. A date alone starts at midnight in the drip’s zone. A past time starts now. One we can’t read is refused with 400 START_AT_INVALID.
spread_daysinteger
Spread first texts evenly over this many days, up to 60.
pace_per_dayinteger
Or send about this many first texts a day: the days are worked out from how many were really added, up to 60. Wins over spread_days.
add_tagstring
Tag every contact this call found, including the ones skipped because they’re already in it. tagged says how many.
activate_draftboolean
true turns a draft drip on before adding contacts. If it can’t, nobody is added (409 DRAFT_NOT_ACTIVATED).
Returns
data.enrolledinteger
Contacts added. 0, with a 200, when everyone found is already in it.
data.skipped_already_enrolledinteger
Skipped because they’re already in it.
data.skipped_not_textableinteger
Picked by id and skipped because they’re opted out or can’t be texted.
data.skipped_not_foundinteger
Picked by id and skipped because they weren’t found.
Errors
HTTPCodeWhen
400NO_RECIPIENTSNobody was found: none of the ids are your contacts, or the list or filter matched nobody who can be texted.
400NOT_TEXTABLEEvery contact you picked by id has opted out or can’t be texted.
400DRIP_NO_STEPSThe drip has no steps yet. Add one first.
400AUDIENCE_TOO_LARGEA list or filter matched more than 5,000. Narrow it or split it.
400RULES_MATCH_NOBODYThe rules can’t match anyone.
400FILTER_INVALIDWe couldn’t use the filter or rules you sent.
400SEGMENT_NOT_FOUNDThe smart list doesn’t exist. SEGMENT_HAS_NO_RULES when it has no usable conditions.
400START_AT_INVALIDstart_at isn’t a date and time we can read. Nobody was enrolled.
400RESERVED_TAGadd_tag is the tag we add only on an opt-out.
402BILLING_PAST_DUEYour last payment failed. FRAUD_HOLD while your account is under a security review.
403CONSENT_REQUIREDMore than one contact and no consent_attested: true. error_code is attestation_required, as on campaigns.
403CONSENT_LIST_INVALIDThe attestation_id doesn’t match these contacts, or was already used.
409DRAFT_NOT_ACTIVATEDYou sent activate_draft and we couldn’t turn the drip on. Nobody was enrolled.
500ENROLL_CHECK_FAILEDWe couldn’t read the drip or the contacts. Nobody was enrolled; try again.
500DRIP_ENROLL_OUTCOME_UNKNOWNSome may have been added. Check who’s in it before retrying; retrying never adds anyone twice.

#List contacts in a drip

One page of the contacts in a drip campaign, newest first, with where they are in it. A drip can hold more than one page, so page on with offset until a page comes back shorter than limit.

Parameters

idinteger, pathRequired
The drip campaign id.
limitinteger, queryDefault 500
From 1 to 500.
offsetinteger, queryDefault 0
How many to skip.
Returns
data[]object[]
One per contact: contact_id, first_name, last_name, phone, email, status, current_step, enrolled_at, next_send_at, and last_skip_detail when a text was skipped.

#Set a drip’s status

Turns a drip campaign on (active), pauses it (paused), stops it (completed) or moves it to drafts (draft). Only an active drip sends; under any other status contacts keep their place and get nothing. Any status can change to any other, so a drip you stopped can be turned back on and its contacts carry on. A drip that stopped itself because it reached its stop rule has no contacts left in it; add them again after you turn it on. Texts that came due while it was off are spread out when it comes back on, not sent all at once. On a very big drip that can take longer than one request: you get 202 with turning_on: true, the drip stays off and sends nothing until its texts are spread out, and then it turns on by itself. This doesn’t change the automation that adds contacts to the drip. Needs the send_sms permission.

Parameters

idinteger, pathRequired
The drip campaign id.
statusenumRequired
active, paused, completed or draft.
Errors
HTTPCodeWhen
400invalid_fieldstatus missing or not one of those.
500DRIP_STATUS_WRITE_FAILEDNothing was changed. Try again.

#Send due texts early

Moves the soonest scheduled sends of an active drip to now, so they go out on the drip’s next pass instead of on their day. It releases at most one day’s pace: the number of contacts due on the next scheduled day after today, less what the drip already sent today and what this call released earlier today (days in the drip’s time zone). The texts go through the same checks as any drip text (sending hours, quiet hours, opt-outs) and are charged like any drip text. Needs the send_sms permission.

Parameters

idinteger, pathRequired
The drip campaign id.
previewboolean
true returns ready, how many it would release, and moves nothing.
Returns
data.releasedinteger
Sends moved to now. With pace and sent_today.
data.reason_codestring
When nothing was released: SEQUENCE_PAUSED (the drip isn’t active), NOTHING_SCHEDULED or DAILY_PACE_REACHED, with a sentence in reason.
Errors
HTTPCodeWhen
503SEND_NOW_COUNT_UNAVAILABLEWe couldn’t count today’s sends, so nothing was moved. Try again.
500SEND_NOW_OUTCOME_UNKNOWNSome may already be on their way. Check the drip’s sent texts before trying again. Also a 503.

#Remove contacts from a drip

Removes contacts who haven’t been sent anything from this drip yet, so you can fix a wrong batch and add it again. Anyone already texted stays in: to take one of them out, stop the contact’s drip. Needs the send_sms permission.

Parameters

idinteger, pathRequired
The drip campaign id.
modeenumDefault all
all, after (added after enrolled_after) or keep (keep the first keep_first).
enrolled_afterstring
An ISO 8601 date and time, such as 2026-11-13T14:00:00-05:00. Required for mode: "after". Send it with Z or an offset: a time without one is read as UTC, not in the drip’s time zone like start_at.
keep_firstinteger
Required for mode: "keep". A whole number, 0 or more.
previewboolean
true returns the count without removing anyone.
after, throughinteger
Only when the last call answered more: true: send back the after and through it gave you, with the same mode, and the removal carries on where it stopped. Leave both out on the first call.
Returns
removedinteger
How many this call removed.
moreboolean
true when one call ran out of time on a very big drip. Call again with after and through until it’s false. Contacts added after the first call aren’t removed.
Errors
HTTPCodeWhen
400ENROLLED_AFTER_REQUIREDmode: "after" without a valid time.
400KEEP_FIRST_REQUIREDmode: "keep" without a whole number in keep_first.
400DRIP_REMOVE_UNSENT_CURSORafter or through isn’t a whole number, or only one of them was sent.

#Delete a drip campaign

Deletes the drip, its steps and every contact’s place in it, all at once or not at all. Nobody gets another text from it. It also deletes the automation the app made to add contacts to this drip; your own automations that add contacts to it are kept. It can’t be undone. Needs the send_sms permission, and a team member must be the owner or an admin. Returns {"success": true}.

Parameters

idinteger, pathRequired
The drip campaign id.
Errors
HTTPCodeWhen
400invalid_fieldThe id isn’t a whole number.
403DRIP_DELETE_OWNER_ONLYYour team’s drip, and you aren’t the owner or an admin.
404DRIP_NOT_FOUNDNo such drip, or it was already deleted.
500DRIP_DELETE_FAILEDNothing was changed. Try again.
500DRIP_DELETE_OUTCOME_UNKNOWNWe couldn’t confirm it. List your drips before you try again.

#Stop a contact’s drip

Takes one contact out of a drip, or out of every drip they are in, even after they were texted. No more steps are sent to them. It works the same as the Remove from drip campaign step in an automation. Needs the send_sms permission and a live key. The path starts with /zapier because the Zapier and n8n apps call it too.

Parameters

contact_idinteger
The contact. Send this or phone.
phonestring
The contact’s phone number, used when there is no contact_id. It must match the saved number exactly once both are formatted.
sequence_idinteger or "all"Default "all"
The drip campaign id (sequence is our older name for it). Send "all" or leave it out to stop every drip the contact is in.
Returns
data.contact_idinteger
The contact.
data.sequence_idinteger or "all"
What was stopped.
data.stoppedinteger
How many enrollments were stopped. 0 when the contact was not in a running drip, so it is safe to call again. A finished drip is left as it is.
Errors
HTTPCodeWhen
400No contact_id or phone, or sequence_id isn’t a whole number or "all".
404No contact on your account matches, or the drip campaign isn’t yours.
500The stop failed and nothing changed. Try again.

#Step types

step_typeWhat it doesFields
send_smsSends a text, charged like any drip text.message_body is the text, with merge fields like {{contact.first_name}}. Optional media_url (a public link) makes it a picture message. When you save the steps, a step that leaves out media_url keeps the picture stored at its position, so send "media_url": null for a step with no picture. Optional allow_empty_merge: true sends it even when a merge field is blank for a contact; without it, that contact skips the step.
if_elseChecks the contact and jumps.then_step_order when it matches, else_step_order when not. One condition is condition_field, condition_op and condition_value; several are conditions, a list of {field, op, value} with condition_combinator all or any. op is equals, not_equals, contains, not_contains, exists, not_exists, gt, gte, lt or lte. A field is a contact field, tags, lead_score, days_since_contact, reply_text (their last reply) or custom:<name>. conditions may also be a {match, conds} filter, the form the app’s builder saves.
wait_replyWaits for the contact to reply, then jumps by what they said.reply_other_step_order is where any reply goes, and timeout_step_order after wait_timeout_minutes with no reply. To send a yes or a stop or a no somewhere else, add reply_yes_step_order or reply_stop_step_order; a yes with no yes step takes reply_other_step_order, and a stop or a no with no stop step ends the drip. With no timeout it waits 90 days, then the drip is finished for them. Optional neg_keywords, comma separated, adds words that count as a stop. Optional pos_keywords, comma separated, replaces the built-in yes words, so list every word that should count as yes.
waitWaits, then moves on.wait_timeout_minutes, or its delay_* fields.
add_tagTags the contact.message_body is the tag.
move_pipelineMoves the contact to a pipeline stage.message_body is the stage id.
assign_userAssigns the contact to a team member.message_body is their user id.
convo_statusChanges the conversation in your inbox.message_body is a comma separated list of read, unread, archive, unarchive, star, unstar.
notifyAlerts your team.message_body is JSON {"channel", "text"}, channel being inapp, email or sms. An sms alert with "to" texts that number, which must be a phone on your team; without it, your own phone. Plain text is an in-app alert.
webhookSends the contact to a web address. Never holds up the drip.message_body is the URL, or JSON {"url", "method", "headers", "custom"}. Only public https:// or http:// addresses.
endFinishes the drip for the contact. Put one at the end of each branch, or the contact runs on into the next step.None.

A branch field left empty, or set to 0, finishes the drip for the contact when it is taken. On a wait_reply, a yes with no yes branch takes the other branch. A save is refused, with step_index (counting from 0) naming the step, when:

HTTPCodeWhen
400DRIP_STEP_TYPE_INVALIDA step_type that isn’t one of these.
400DRIP_STEP_INVALIDA step that isn’t an object.
400DRIP_GRAPH_LOOPThe branches loop back to an earlier step.
400DRIP_STEP_EMPTYA send_sms step with no text has another step after it. One at the very end is dropped instead.
400DRIP_STEP_DELAY_INVALIDA wait below 0 or over 10 years.
400DRIP_STEP_BODY_INVALIDmessage_body isn’t text.
400no codeA media_url or webhook address we can’t use. error says why. Also when every step is empty.
400CONTENT_BLOCKEDA text breaks our content rules.