Skip to content

Campaigns ​

A campaign is an automated email sequence. It is made of steps (emails, delays, conditions) and sent to enrolled contacts.

Lifecycle ​

draft ──start──▶ active ──pause──▶ paused ──resume──▶ active ──▶ completed
                                      └──────start──────┘
ActionAllowed fromError otherwise (422)
startdraft, pausedCampaign cannot be started from current status
pauseactiveOnly active campaigns can be paused
resumepausedOnly paused campaigns can be resumed
update (PUT)anything except activeCannot update active campaign

A campaign must have at least one step to start (422 Campaign must have at least one step).

start and resume run the same check before launch as the app. A blocking point refuses the action with a 422 that lists the blocking points: no email in the sequence, an email without subject or body, no enrolled contact who can start the sequence (launch only), no active sender that can send, no sending day or an empty sending window. Waits (sending window closed, daily limits reached) and points to check do not refuse it. Starting or resuming never enrolls contacts and never sends an email by itself: the sending engine takes the campaign afterwards, in its sending window.

json
{
  "error": "HttpError",
  "message": "Campaign cannot be started: No active sender with a daily limit above 0: nothing can be sent.",
  "errors": { "launch": [ { "code": "no_sender", "object": "Senders", "message": "No active sender with a daily limit above 0: nothing can be sent." } ] },
  "code": 422
}

The campaign object ​

json
{
  "id": 12,
  "name": "Q2 outbound CTOs",
  "status": "active",
  "settings": {},
  "sending_settings": {},
  "stats": {
    "total": 200, "pending": 120, "in_progress": 60,
    "completed": 15, "failed": 2, "unsubscribed": 3
  },
  "started_at": "2026-04-01T08:00:00.000000Z",
  "completed_at": null,
  "created_at": "2026-03-28T10:00:00.000000Z",
  "updated_at": "2026-04-01T08:00:00.000000Z",
  "steps": [ /* Step */ ],
  "contact_lists": [ /* List */ ],
  "segments": [ /* Segment */ ]
}
  • stats is included only when you add ?include_stats=1.
  • steps, contact_lists and segments are included on GET /campaigns/{id}. Create, update and duplicate include the lists and segments.

Endpoints ​

MethodPathPermission
GET/campaignscampaigns:read
POST/campaignscampaigns:write
GET/campaigns/{id}campaigns:read
PUT/campaigns/{id}campaigns:write
POST/campaigns/{id}/startcampaigns:send
POST/campaigns/{id}/pausecampaigns:send
POST/campaigns/{id}/resumecampaigns:send
POST/campaigns/{id}/duplicatecampaigns:write
GET/campaigns/{id}/statscampaigns:read
GET/campaigns/{id}/analyticscampaigns:read
GET/campaigns/{id}/timelinecampaigns:read
GET/campaigns/{id}/exportcampaigns:read

List campaigns ​

GET /campaigns?status=active&include_stats=1

Returns campaigns newest first. The list is paginated.

Create a campaign ​

POST /campaigns
FieldTypeRules
namestringRequired. Max 255 characters
settingsobjectFree-form campaign settings
list_idsinteger[]Lists in your workspace to target
segment_idsinteger[]Segments in your workspace to target

The campaign is created in draft.

Attaching lists or segments only sets the campaign's targets. It does not enroll anyone. To enroll contacts, call POST /campaigns/{id}/contacts/enroll-from-lists.

201: { "data": Campaign }

Update a campaign ​

PUT /campaigns/{id} accepts the same fields as create, and every field is optional.

list_ids and segment_ids replace the current targets. Send [] to clear them.

Typical flow ​

bash
# 1. Create the campaign
curl -X POST https://app.beeving.com/api/v1/campaigns \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"name": "Q2 outbound", "list_ids": [1]}'

# 2. Define the sequence
curl -X POST https://app.beeving.com/api/v1/campaigns/12/steps/bulk-update \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"steps": [
        {"type": "email", "settings": {"subject": "Hi {{first_name}}", "body": "<p>...</p>"}},
        {"type": "delay", "settings": {"delay_value": 3, "delay_unit": "days"}},
        {"type": "email", "settings": {"template_id": 7}}
      ]}'

# 3. Enroll the contacts
curl -X POST https://app.beeving.com/api/v1/campaigns/12/contacts/enroll-from-lists \
  -H "Authorization: Bearer TOKEN" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"list_ids": [1]}'

# 4. Start
curl -X POST https://app.beeving.com/api/v1/campaigns/12/start \
  -H "Authorization: Bearer TOKEN" -H "Accept: application/json"

Duplicate ​

POST /campaigns/{id}/duplicate

Creates a draft copy named "<name> (Copy)". The copy includes the settings, steps, lists and segments, but no enrolled contacts.

201: { "data": Campaign }

Stats ​

GET /campaigns/{id}/stats
json
{
  "data": {
    "sent": 180, "delivered": 176, "delivery_confirmed": 0, "bounced": 4,
    "opened": 92, "unique_opens": 92, "total_opens": 140,
    "clicked": 25, "unique_clicks": 25, "total_clicks": 31,
    "replied": 12, "positive_replies": 5, "unclassified_replies": 2, "unsubscribed": 2,
    "delivery_rate": 97.8, "open_rate": 51.1, "click_rate": 13.9,
    "bounce_rate": 2.2, "click_through_rate": 27.2,
    "reply_rate": 6.7, "positive_reply_rate": 2.8, "unsubscribe_rate": 1.1,
    "reply_categories": { "positive": 5, "out_of_office": 3, "unclassified": 2, "not_now": 2 }
  }
}

Rates are percentages of the emails sent, rounded to one decimal place, except click_through_rate (clicked / opened). The unit is the email: opened, clicked and replied count emails with at least one such interaction, total_opens and total_clicks count every tracking event. delivered means that no bounce was received; inbox placement is not measured. See metric definitions.

Analytics ​

GET /campaigns/{id}/analytics?days=14

Returns the same stats, plus a day-by-day timeline that starts on the campaign's start date and covers days days, up to today. Each series counts emails: sent on that day, or opened, clicked or replied for the first time on that day:

json
{
  "data": {
    "stats": { /* as above */ },
    "timeline": {
      "categories": ["1 Apr", "2 Apr"],
      "series": [
        { "name": "Emails sent", "data": [40, 38] },
        { "name": "Emails opened", "data": [12, 21] },
        { "name": "Emails clicked", "data": [2, 4] },
        { "name": "Emails replied", "data": [0, 1] }
      ]
    }
  }
}

Timeline ​

GET /campaigns/{id}/timeline?per_page=50

Returns the emails sent, newest first. The list is paginated.

json
{
  "data": [{
    "id": 901,
    "contact": { "id": 42, "email": "alice@example.com", "full_name": "Alice Martin" },
    "step": { "id": 3, "type": "email", "subject": "Hi Alice" },
    "status": "delivered",
    "sent_at": "...", "delivered_at": "...", "opened_at": "...", "clicked_at": null,
    "open_count": 2, "click_count": 0
  }],
  "meta": { "current_page": 1, "last_page": 4, "per_page": 50, "total": 180 }
}

The email status is one of queued, sending, sent, delivered, bounced, complained or failed.

Export ​

GET /campaigns/{id}/export

Returns a CSV file named campaign-{id}-results.csv, with one row per email sent.

API v1.0