API reference / Outreach
Campaigns
A campaign sends one message to every contact on one or more lists. It ties the lists, the content and the sender together so you can schedule, start, pause and measure them as one. To send to one person, see Sending basics or Email.
type |
Sends |
|---|---|
rvm |
Ringless voicemail |
sms |
Text, or RCS with rcs_content |
email |
|
voice_broadcast |
A call that plays one message to a person and another to a machine |
ai_broadcast |
Calls handled by one of your AI agents |
Starting a campaign is not delivery. Each contact's result arrives on the status webhook for the channel:
contact.rvm.status,contact.sms.statusorcontact.email.status. Calling hours, the contact frequency limit and retries are explained in Send lifecycle. What eachreason_codemeans, and what to do about it, is in Outcomes and what to do.
Routes
| Method | Route | Scope | What it does |
|---|---|---|---|
GET |
/campaign/public/campaigns |
campaigns:read |
List campaigns |
POST |
/campaign/public/campaigns |
campaigns:write |
Create a campaign |
GET |
/campaign/public/campaigns/{id} |
campaigns:read |
Get a campaign |
PUT |
/campaign/public/campaigns/{id} |
campaigns:write |
Update a campaign or schedule its start |
DELETE |
/campaign/public/campaigns/{id} |
campaigns:write |
Delete a campaign |
POST |
/campaign/public/campaigns/{id}/start |
campaigns:send |
Start or resume sending |
POST |
/campaign/public/campaigns/{id}/pause |
campaigns:send |
Pause sending |
GET |
/campaign/public/campaigns/{id}/stats |
campaigns:read |
Get delivery counts |
GET |
/campaign/public/balance |
balance:read |
Get your balance |
List campaigns
| Field | Type | Required | Description |
|---|---|---|---|
skip |
integer | No | How many to skip |
limit |
integer | No | How many to return |
search_term |
string | No | Match on the campaign name |
status |
string | No | not_started, active, paused, complete or insufficient_credit |
type |
string | No | One of the types above |
curl "https://api-v2.dropcowboy.com/campaign/public/campaigns?type=sms&status=active" \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET"
Create a campaign
Send type and a campaign_data object. Always send type: without it, the
channel is guessed from the fields in campaign_data.
curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns \
-H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
-H "Content-Type: application/json" \
-d '{
"type": "rvm",
"campaign_data": {
"name": "Spring reminder",
"list_ids": ["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],
"phone_line_id": "e2b6f9a3-5c1d-4e8b-a4f7-9c3e1b5d7a28",
"brand_id": "2c9d4e1f-7a3b-4f6c-8d2e-9b1a5c7e3f40",
"media_id": "1b7e3c9a-4d2f-4a8b-9e6c-7f2a1d5b3c80"
}
}'
A new campaign answers 201:
{
"data": {
"campaign_id": "a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b",
"approved": true,
"started": false
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
approved says whether the campaign passed compliance review. started is
true only when you set method to immediate and the campaign was approved
on creation: it begins sending right away. Otherwise, start it with
Start a campaign.
A campaign's state is campaign_data.status: not_started after create, then
active, paused, complete, or insufficient_credit when your balance
ran out.
campaign_data fields
| Field | Type | Used by | Description |
|---|---|---|---|
name |
string | All | Campaign name |
list_ids |
string[] | All | Lists to send to. GET /contact/public/lists |
phone_line_id |
string | Voice, text | Sender numbers and return-call routing. GET /phone/public/lines |
brand_id |
string | Voice, text | Registered brand. GET /automation/public/brands |
media_id |
string | rvm |
Audio file. GET /media/public/media |
tts_body, voice_id |
string | rvm |
Text to speech and its voice. GET /voice/public/voices |
sms_body |
string | sms |
The text |
rcs_content |
object | sms |
RCS card, usually copied from a template. GET /template/public/templates?type=rcs |
media_on_speech, media_on_beep, tts_on_speech, tts_on_beep |
string | voice_broadcast |
Messages for a person and for a machine. The full set is in Send a voice broadcast. |
ai_agent |
object | ai_broadcast |
{ "agent_id": "..." }, a published agent. GET /agents/public/agents |
email_subject, email_html, email_preheader |
string | email |
Content |
email_from_mailbox_id |
string | email |
Sending mailbox. GET /domain/public/mailboxes |
email_from, email_from_name, email_reply_to |
string | email |
Sender details when you don't use a mailbox |
email_template_id |
string | email |
Saved template. GET /template/public/templates?type=email |
method |
string | All | immediate (default) or drip to spread sends over time |
Fields we don't recognize are saved without an error, so a misspelled field is ignored without an error. Read the campaign back after you create it.
An email campaign:
{
"type": "email",
"campaign_data": {
"name": "Spring newsletter",
"list_ids": ["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],
"email_from_mailbox_id": "c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35",
"email_subject": "Spring hours",
"email_html": "<p>We are open Saturdays this spring.</p>"
}
}
Compliance review
We score each campaign for compliance risk when you create it. Low-risk
campaigns are approved straight off. Others wait for a Drop Cowboy® reviewer,
with approved: false until then, and can't start. Create campaigns ahead of
the time you need them.
Editing what a contact receives sends the campaign back through review: the
message, audio, voice, AI agent, lists, brand, caller ID or sending numbers.
A campaign that was sending and isn't approved again straight off is paused
until a reviewer approves it; start it again after that. Edits to other
fields, such as the name, keep the current approval. You can't set risk or
approved yourself; we ignore them on create and update.
Get a campaign
GET /campaign/public/campaigns/{id} returns the campaign with its
campaign_data, approved, delivery_type and deliver_at. An unscheduled
campaign has deliver_at 9007199254740991.
Update a campaign
PUT /campaign/public/campaigns/{id} replaces the whole campaign_data
object. Any field you leave out is removed. Get the campaign first, change
the fields you need, and send the complete object back. campaign_data is
required.
Add deliver_at, in epoch milliseconds, to schedule the start.
{
"campaign_data": {
"name": "Spring reminder (final)",
"list_ids": ["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],
"phone_line_id": "e2b6f9a3-5c1d-4e8b-a4f7-9c3e1b5d7a28",
"brand_id": "2c9d4e1f-7a3b-4f6c-8d2e-9b1a5c7e3f40",
"media_id": "1b7e3c9a-4d2f-4a8b-9e6c-7f2a1d5b3c80"
},
"deliver_at": 1774270800000
}
Delete a campaign
DELETE /campaign/public/campaigns/{id} deletes the campaign.
Start a campaign
POST /campaign/public/campaigns/{id}/start starts sending to the campaign's
lists. On a paused campaign it resumes sending. A campaign still saved as a
draft in the dashboard can't be started and returns 400.
{
"data": { "campaign": { "campaign_id": "a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b" }, "started": true },
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
started is false when nothing was queued. Starting a campaign that
already finished returns it unchanged with started: true.
A campaign still waiting for compliance review doesn't start. You get 200
with approved: false and started: false, and the campaign stays in the
review queue. Start it again once GET shows approved: true.
{
"data": { "campaign_id": "a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b", "approved": false, "started": false },
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
Pause a campaign
POST /campaign/public/campaigns/{id}/pause pauses sending. Messages already
handed to the carrier can still arrive. Start the campaign again to resume.
Get campaign stats
GET /campaign/public/campaigns/{id}/stats returns delivery counts.
| Field | Type | Required | Description |
|---|---|---|---|
start_ts |
integer | No | Count from this time, in epoch milliseconds |
end_ts |
integer | No | Count up to this time, in epoch milliseconds |
bucket_type |
string | No | Which roll-up to count from: 5min, hourly or daily (default). Finer roll-ups update sooner. |
{
"data": {
"sent": 330,
"delivered": 318,
"failed": 12,
"pending": 12,
"read": 0,
"billable": 318,
"non_billable": 12,
"delivery_rate": 0.9636,
"failure_rate": 0.0364,
"read_rate": 0
},
"meta": { "request_id": "c2a6e8f4-3b9d-4c1e-8a7f-5d3b1e9c6a24" }
}
read comes only from RCS and some carriers, so treat read_rate as a
minimum. Email campaigns also return opened, clicked, bounced,
complained, unsubscribed, open_rate and click_rate. unsubscribed
counts each contact once, however many of the campaign's emails or links they
used.
Get your balance
GET /campaign/public/balance returns the one balance that pays for every
channel. See Get your balance for the
response. A contact who can't be
sent to for lack of funds fails with 3000 (No Funds).
Delivery rules
Starting a campaign doesn't mean every contact gets the message. Each contact
is checked as it's sent, under the same
delivery rules as a single send: calling
hours, the contact frequency limit, consent and the do-not-contact list. Each
skipped contact gets its own reason_code; see Outcomes.
- A texting campaign that reaches the daily limit carriers set for its
registered texting campaign stops for the day. The contacts it hadn't
reached yet get no result that day; the campaign switches to
delivery_type: retry, withdeliver_atat 9 a.m. Pacific the next day, and sends to them then. A campaign that had finished reopens to do this. - Email campaigns follow CAN-SPAM and the email frequency limit.
- Test numbers and test email addresses on the Dialing rules page skip the frequency limits, so you can test a campaign on yourself more than once.
Results
- Each contact's result arrives on the channel's status webhook:
contact.rvm.statusfor ringless voicemail, voice broadcasts and AI calls,contact.sms.statusfor texts, orcontact.email.status. - The campaign sends
campaign.startedandcampaign.completed. - AI calls also send one
ai_agent.*event per call.
See Webhooks to subscribe.
Errors
| Status | Cause | What to do |
|---|---|---|
400 |
You started a campaign that's still a draft | Finish the campaign in the dashboard, then start it. |
402 |
The account is past due | Update billing; see Payment required. |
403 with trial_feature_blocked |
Your brand isn't verified | Verify your brand in the Trust Center, then retry. |
403 |
A trial limit was reached, or the account is inactive | Upgrade or reactivate the account. |
404 |
The campaign id isn't on your account, or the campaign was deleted | Check the id with GET /campaign/public/campaigns. |
For everything else, see Responses, errors and limits.
Common problems
- The campaign won't start. If start returned
started: falsewithapproved: false, the campaign is waiting for compliance review. Start it again once it's approved. - A running campaign paused after an edit. You changed what contacts receive, so it went back to review. Start it again once it's approved.
- A field disappeared after an update.
PUTreplaces all ofcampaign_data. Send the complete object. - Contacts were skipped. Read each contact's
reason_code. Most skips are calling hours (4011), the contact frequency limit (4013), or missing consent. - The campaign stopped with
insufficient_credit. Add funds, then start it again.
For more symptoms and fixes, see Troubleshooting.
While Drop Cowboy provides tools to support compliance efforts, customers remain solely responsible for obtaining proper consent, maintaining opt-out lists, and complying with all federal and state telemarketing regulations. Consult with your legal counsel to ensure your specific use case and consent mechanisms comply with applicable laws.