Send and control campaigns
#Send a campaign
Sends a draft, scheduled, paused or failed campaign now, at your per-segment rate for each recipient (check it first with quote). A send that goes out at once is charged from your balance before the first text; large sends go out over time at your account’s sending speed and are charged as each batch goes out. A paused campaign skips everyone it already texted, and a failed one skips everyone whose text didn’t fail. Needs the send_sms permission.
Parameters
consent_attestedbooleanRequired- Must be
true, unless you sendattestation_id: your confirmation that every recipient agreed to get texts from you (consent). attestation_idinteger- Instead of
consent_attested: a consent record already made on your account for exactly this audience, less than 30 minutes old and not used yet. send_nowbooleantrueskips the automatic spreading of a large send over time.send_capinteger- Send to only this many. Use
affordable_recipientsfrom aninsufficient_balanceanswer.
More parameters (4)
Some sends answer 200 with success: false and a needs_confirmation field instead of sending. Nothing was saved, charged or sent. Read message, then repeat the same request with one of these answers.
confirm_empty_mergeboolean- Answer to
empty_merge_field, when some recipients have no value for a merge field like{{contact.first_name}}.truesends with a blank there. skip_empty_mergeboolean- Answer to
empty_merge_field.trueskips those people. confirm_duplicateboolean- Answer to
duplicate_campaign, when anyone in this audience got the same or a 90%-similar text from another campaign within 7 days.truesends anyway. duplicate_exclude_blast_idinteger- Answer to
duplicate_campaign: passduplicate.other.idfrom that answer to send only to people who weren’t in that campaign.
Returns
data.statusstringsending.data.total_recipientsinteger- How many it’s going to.
data.staggeredbooleantruewhen it goes out over time.data.scheduledis then how many we queued.data.interval_secinteger- Seconds between texts, when the send is paced: your
drip_interval_sec, or a pace we picked to spread a large send. A campaign withsmart_send_enabled, or with both ends of a delivery window set, goes out over time without it.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no_recipients | Nobody in the audience can be texted. |
| 400 | AUDIENCE_TOO_LARGE | The audience is over 500,000 recipients, the most one campaign sends to. Nothing is sent. Narrow it or split it into smaller campaigns. |
| 400 | no_active_number | You have no active number to send from. |
| 400 | already_sent | It finished or was stopped. Duplicate it to send again, or use Send to the people it missed. |
| 400 | ALL_ALREADY_SENT | Everyone it would reach already got it. |
| 402 | insufficient_balance | Your balance doesn’t cover even one recipient, or doesn’t cover the send_cap you set. On a send that goes out at once without send_cap, a short balance sends to as many people as it covers and data.balance_capped_from is the full audience. To text the rest, top up, then add the same audience to the campaign: anyone who already got it is left out. The answer has needed, affordable_recipients and total_recipients. |
| 402 | monthly_limit | It would pass your monthly text limit. |
| 402 | BILLING_PAST_DUE | Your last payment failed. Update your card in Billing. |
| 403 | attestation_required | No consent_attested: true and no attestation_id. |
| 403 | attestation_* | The attestation_id doesn’t fit: the code names why, like attestation_expired or attestation_list_hash_mismatch. |
| 409 | already_sending | It’s already sending. |
| 409 | send_in_progress | Another request is already sending it. |
| 422 | CONTENT_BLOCKED | The message breaks the content policy. reasons lists why. Nothing is sent. |
| 500 | SEND_UNCONFIRMED | Something broke after sending may have started. Check the campaign instead of sending again. |
| 500 | SEND_FAILED | Nothing was charged or sent. Retry. |
| 500 | SEND_FAILED_AFTER_CHARGE | We charged for the send and nothing went out. Schedule the campaign for now to send it with that payment, or delete it to get the charge back. |
| 503 | BILLING_UNAVAILABLE | Nothing was charged or sent. Retry in a moment. |
#Pause a campaign
Pauses a scheduled campaign, or one sending over time. Texts already sent stay sent. Needs the send_sms permission. Returns the campaign as saved (the saved shape) with status: "paused".
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | not_pausable | It’s sending all at once. Use stop. |
| 409 | state_changed | Its status changed first. Check it and try again. |
#Resume a campaign
Picks a paused campaign back up where it left off. Resuming is free; texts still to go that weren’t paid for when the send started are charged as they go out. Needs the send_sms permission. Returns the campaign as saved (the saved shape).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | not_paused | It isn’t paused. |
| 409 | resume_conflict | It changed while we resumed it. |
| 503 | resume_failed | Still paused, nothing sent. Try again. |
#Unschedule a campaign
Turns a scheduled campaign back into a draft. The older path /campaigns/:id/cancel still works and does the same. Neither stops a campaign that is sending; use stop for that. Nothing was charged yet, so there’s nothing to refund. Needs the send_sms permission. Returns the campaign as saved (the saved shape) with status: "draft".
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | already_sending | It started sending. Use stop. |
| 400 | not_scheduled | It isn’t scheduled. |
| 409 | state_changed | Its status changed while you unscheduled it. Check it and try again. |
#Stop a campaign
Stops a sending or paused campaign for good. Texts already sent stay sent. You aren’t charged for people it never reached. On a campaign that is sending, the stop is seen within the next 100 or so recipients (per sending lane), so a few more texts can go out and the answer may still show status “sending”; it turns “completed” once the send stops. A paused campaign stops at once. Needs the send_sms permission. Returns the campaign as saved (the saved shape).
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | not_stoppable | It’s a draft, scheduled (use unschedule) or already finished. |
| 500 | stop_failed | The stop didn’t take. Call it again. |
| 500 | stop_unconfirmed | We couldn’t confirm the campaign stopped. Get the campaign, and call it again if it’s still sending. |
#Get your sending speed
The most texts per second your account can send. Large campaigns are paced to it.
Returns
cap_per_secinteger- Your limit.
fallbackinteger- The speed we use if we can’t read yours.
#Send to the people it missed
Finishes a completed or failed campaign whose send was cut off: it texts the recipients who have no text from it yet, in the background. Recipients the first send already paid for aren’t charged again; the rest are charged from your balance at your per-segment rate before they go out. If your balance doesn’t cover them, nothing more is sent. Opted-out people are still skipped. A campaign you stopped is refused, and so is one that never texted anyone (send that one with Send a campaign). Needs the send_sms permission.
Returns
data.missedinteger | null- About how many it’s sending to, or
nullwhen we couldn’t count them. data.notestring- What happens next, in a sentence.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | never_sent | Nobody got it yet. Nothing was sent. |
| 400 | still_sending | It’s still sending. Try again once it has made no progress for a while. |
| 404 | CAMPAIGN_NOT_FOUND | No campaign with that id on your account, or you deleted it. |
| 409 | blast_was_stopped | You stopped it, so we won’t text the people it hadn’t reached. |
| 409 | ALREADY_SENDING | Another request is already sending the rest. |
| 503 | CAMPAIGN_BUSY | Nothing changed. Try again in a few seconds. |
| 500 | FINISH_UNCONFIRMED | Something broke after sending may have started. Check the campaign before you try again. |
#Send the rest now
For a campaign going out over time: moves every text still waiting to now and turns its pace off. Texts already picked up for the current batch aren’t moved, and waiting texts still keep to your delivery window. This call only changes when the texts go; each is charged as it goes out, the same as before. A campaign with nothing waiting answers flushed: 0. Needs the send_sms permission.
Returns
data.flushedinteger- How many waiting texts were moved to now.
data.notestring- What happened, in a sentence.
Errors
| HTTP | Code | When |
|---|---|---|
| 404 | CAMPAIGN_NOT_FOUND | No campaign with that id on your account. |
| 409 | campaign_paused | It’s paused. Resume it first. |
| 409 | not_sending | It’s a draft or scheduled and hasn’t started sending yet. Nothing changed, and its pace is kept. |
| 500 | SEND_REMAINING_FAILED | Nothing changed. Try again. |
#Change a campaign’s pace
Sets how fast a campaign sends. On a draft or scheduled campaign it saves the pace for when it starts. On a campaign sending over time it re-spaces every text still waiting, starting now, inside your delivery window. Free; it doesn’t change what the texts cost. Send one of the fields below. Needs the send_sms permission.
Parameters
interval_secinteger- Seconds between texts. Read first when you send it.
per_hournumber- Texts per hour.
per_second,per_minuteandper_daywork the same way. Withoutinterval_sec, the first ofper_second,per_minute,per_hour,per_daythat is above 0 counts.
Every pace becomes whole seconds between texts, rounded up so it is never faster than you asked, from 2 seconds to 7 days.
Returns
interval_secinteger- The seconds between texts we saved.
per_hournumber- The same pace per hour.
pendinginteger- Texts still waiting.
0before the campaign starts. eta_hoursnumber- About how long the waiting texts take. Only on a sending campaign; a campaign that hasn’t started answers
mode: "prestart"instead.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | PACE_REQUIRED | None of the pace fields was a usable number. |
| 400 | PACE_NOT_EDITABLE | It isn’t a draft, scheduled, or sending over time. |
| 400 | NOTHING_PENDING | It’s sending, but nothing is waiting. |
| 404 | CAMPAIGN_NOT_FOUND | No campaign with that id on your account. |
| 409 | CAMPAIGN_STARTED | It started sending while we saved. Nothing changed; try again. |
| 503 | PENDING_READ_FAILED | Nothing changed. Try again in a minute. |
#Add recipients
Texts the same campaign to more people. It works on a campaign sending over time, a completed one (which starts sending again), or a scheduled one with hand-picked recipients. Anyone already in the campaign is left out. This call charges nothing itself: people added to a sending or completed campaign join the end of its queue at its pace and are charged as each batch goes out, at your per-segment rate, and a queued 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). People added to a scheduled campaign are charged when it sends. Needs the send_sms permission. The Campaign object says when this works in can_add_recipients.
Parameters
Pick the people with the same audience parameters as Create a campaign: recipient_filter, segment_id, recipient_rules or recipient_contact_ids.
consent_attestedbooleanRequired- Must be
true: your confirmation that everyone you add agreed to get texts from you (consent).
Returns
addedinteger- How many were added.
skipped_existinginteger- How many were already in the campaign.
total_recipientsinteger- The campaign’s audience now.
resumedbooleantruewhen a completed campaign started sending again.compliance_surcharge_usdnumber- An estimate of the compliance surcharge for the people added. It’s 0 when the estimate couldn’t be read, so 0 doesn’t promise there’s no charge: each wave is billed as it sends. Not on a scheduled campaign.
compliance_per_segment_usdnumber- The compliance surcharge per segment behind that estimate. Not on a scheduled campaign.
eta_hoursnumber | null- About how long until everyone waiting is texted. Not on a scheduled campaign.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | CONSENT_REQUIRED | No consent_attested: true. |
| 400 | campaign_not_extendable | It’s a draft, paused, sending all at once, failed, or scheduled with a filter or smart list audience. error says which. |
| 400 | blast_was_stopped | You stopped it. Duplicate it to reach these people. |
| 400 | NO_ELIGIBLE_RECIPIENTS | Nobody you picked can be texted. |
| 400 | ALREADY_IN_CAMPAIGN | Everyone you picked is already in it. If you just retried, your first add went through. |
| 402 | BILLING_PAST_DUE | Your last payment failed. Update your card in Billing. |
| 404 | CAMPAIGN_NOT_FOUND | No campaign with that id on your account, or you deleted it. |
| 409 | CAMPAIGN_CHANGED | The scheduled campaign changed or started while we added. Nobody was added. |
| 500 | CAMPAIGN_NOT_RESTARTED | People were added but the completed campaign didn’t start again, so they won’t be texted yet. Contact support; don’t add them again. |
#Campaign results
Reads for one campaign while it sends and after. Any key can call them, test keys included. An id that isn’t a whole number answers 400 INVALID_ID, and one that isn’t a campaign on your account answers 404 CAMPAIGN_NOT_FOUND.
#Get delivery results
Delivery, reply and failure numbers for one campaign, by carrier, and by version for an A/B test. The answer is in data (and repeated at the top level for older clients). A part we couldn’t read is named in unavailable. Totals, carriers and failure reasons are then null, never a zero. The A/B numbers in by_variant read 0 when by_variant_delivery, by_variant_replies or by_variant_optout is in unavailable, so check it before you compare versions. Needs the view_reports permission.
Parameters
partsenum · querytotalsanswers onlycampaign,totalsandby_variant. Faster.
Returns
data.campaignobjectid,nameandstatus.data.totalsobjecttotalsent,delivered,failed,pending,replies(people who replied),positive(positive replies),skipped, anddelivered_pct,failed_pct,pending_pct.data.carrier_breakdownobject[]- Per carrier:
carrier,total,delivered,failed,pending.carrieris one ofverizon,att,tmobile,uscellular,sprint,other,international, or(unknown)when no carrier is on record. These are the same wordsGET /sms/my-delivery-rateuses. Number health shows the display name instead (T-Mobile, Unknown carrier). data.top_errorsobject[]- Up to 5 failure reasons, most common first:
error_reason,error_detail,countand anexplanation. data.by_variantobject[]- Only for an A/B test. Per version:
variant(A,B, ...),body,sent,delivered,failed,replied,positive_replied,optout.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_field | parts is something other than totals. |
| 500 | BREAKDOWN_UNAVAILABLE | We couldn’t read the totals. Try again. |
#Get skipped recipients
Why recipients were left out of a campaign, counted by reason, and the texts held for quiet hours.
Returns
data.reasons[]object[]- Each reason with a count above 0:
reason(likeopt_outorquiet_hours),count, andphrase, a short description ornull. data.heldobject- Texts still waiting for quiet hours to end:
countandnext_at, when the next one goes. data.sent_after_quiet_hoursinteger- Texts held for quiet hours that have since gone out.
Errors
| HTTP | Code | When |
|---|---|---|
| 500 | SKIPS_UNAVAILABLE | We couldn’t read them. Try again. |
#Get the send schedule
For a campaign going out over time: how many texts are still waiting, per day, and when the next and last go. A campaign that sends all at once has nothing waiting, so its lists are empty.
Parameters
tzstring · queryDefault the campaign’s time zone- IANA time zone the days are counted in, like
America/Chicago. Left out or unknown, the days are counted in the campaign’s own time zone, or UTC when it has none.
Returns
data.schedule_14d[]object[]dateandcountfor every day with a text waiting, not only the next 14.data.pending_totalinteger- All texts waiting.
data.queued_nowinteger- Texts already due.
data.next_send_atISO 8601 string | null- When the next waiting text goes.
data.last_send_atISO 8601 string | null- When the last waiting text goes.
Errors
| HTTP | Code | When |
|---|---|---|
| 500 | SCHEDULE_UNAVAILABLE | We couldn’t read it. Try again. |
See also: the guide to doing this in the app.