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 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.status or contact.email.status. Calling hours, the contact frequency limit and retries are explained in Send lifecycle. What each reason_code means, 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, with deliver_at at 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.status for ringless voicemail, voice broadcasts and AI calls, contact.sms.status for texts, or contact.email.status.
  • The campaign sends campaign.started and campaign.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: false with approved: 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. PUT replaces all of campaign_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.

Code samples

The requests from this page, ready to copy. Set DC_KEY and DC_SECRET to your API key pair first.

List campaigns
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
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"
    }
  }'
Create an email campaign
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": "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>"
    }
  }'
Schedule a campaign
curl -X PUT https://api-v2.dropcowboy.com/campaign/public/campaigns/a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b \
  -H "x-key: $DC_KEY" -H "x-secret: $DC_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "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
  }'