List campaigns

GET/api/v1/campaigns

Every outreach campaign, most recently updated first, with member and step counts. Status is draft, active, paused or completed. Returns up to 200.

Example request

curl "https://crowdstack.org/api/v1/campaigns" \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY"

Example response

{
  "campaigns": [
    {
      "id": "cmf3k2x9a0009abcd",
      "name": "Q4 outbound",
      "status": "active",
      "memberCount": 148,
      "stepCount": 3,
      "createdAt": "2026-09-01T09:00:00.000Z",
      "updatedAt": "2026-09-15T10:12:00.000Z"
    }
  ]
}

Get a campaign

GET/api/v1/campaigns/:id

One campaign with its sending settings, the mailboxes it sends from, and every step in order. Step bodies are raw templates with merge fields intact.

Step type is email or task. Email steps are sent by the runner; task steps open a task for a person and taskKind says what kind. delayDays is the wait after the previous step, 0 on the first.

An email step with a non-null bBody is running an A/B test: bSubject and bBody are version B, bNote says what B tries. Each person gets one version, split evenly.

Responses

StatusMeaning
404No campaign with that id.

Example request

curl "https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd" \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY"

Example response

{
  "campaign": {
    "id": "cmf3k2x9a0009abcd",
    "name": "Q4 outbound",
    "status": "active",
    "timezone": "Europe/London",
    "sendWindowStart": 9,
    "sendWindowEnd": 17,
    "weekdaysOnly": true,
    "stopOnReply": true,
    "unsubscribeLink": true,
    "memberCount": 148,
    "mailboxes": [
      {
        "id": "cmf3k2x9a0020abcd",
        "name": "Outbound 1",
        "fromEmail": "jane@acme-mail.com",
        "active": true
      }
    ],
    "steps": [
      {
        "id": "cmf3k2x9a0030abcd",
        "position": 0,
        "type": "email",
        "taskKind": null,
        "delayDays": 0,
        "subject": "Quick question, {{firstName}}",
        "body": "Hi {{firstName | there}},\n\nI put together a short demo of Acme: {{fields.demo_url}}\n\n{{senderName}}"
      },
      {
        "id": "cmf3k2x9a0031abcd",
        "position": 1,
        "type": "email",
        "taskKind": null,
        "delayDays": 3,
        "subject": "",
        "body": "Hi {{firstName | there}},\n\nJust following up on my last note.\n\n{{senderName}}"
      }
    ],
    "createdAt": "2026-09-01T09:00:00.000Z",
    "updatedAt": "2026-09-15T10:12:00.000Z"
  }
}

Create a campaign

POST/api/v1/campaigns

Creates a campaign as a draft, sending from every active mailbox in the workspace, with the steps you give it. Add members, then set status to active to start sending.

Request body

FieldTypeDescription
namerequiredstring—
stepsrequiredobject[]The sequence, in order.
timezonestringIANA zone for the send window. Defaults to Europe/London.
sendWindowStartnumberHour the send window opens. Default 9.
sendWindowEndnumberHour it closes. Default 17.
weekdaysOnlyboolean—
stopOnReplyboolean—
unsubscribeLinkbooleanAdd an opt-out link and List-Unsubscribe headers. Default false.

Responses

StatusMeaning
201Body: { campaign }.
422A step is invalid, or the timezone is unknown.

Example request

curl -X POST https://crowdstack.org/api/v1/campaigns \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q4 outbound",
    "steps": [
      {
        "kind": "email",
        "subject": "Quick question, {{firstName}}",
        "body": "Hi {{firstName | there}},\n\n...",
        "delayDays": 0
      },
      {
        "kind": "email",
        "subject": "",
        "body": "Just following up.",
        "delayDays": 3
      }
    ]
  }'

Example response

{
  "campaign": {
    "id": "cmf3k2x9a0009abcd",
    "name": "Q4 outbound",
    "status": "draft",
    "timezone": "Europe/London",
    "sendWindowStart": 9,
    "sendWindowEnd": 17,
    "weekdaysOnly": true,
    "stopOnReply": true,
    "unsubscribeLink": true,
    "memberCount": 0,
    "mailboxes": [
      {
        "id": "cmf3k2x9a0020abcd",
        "name": "Outbound 1",
        "fromEmail": "jane@acme-mail.com",
        "active": true
      }
    ],
    "steps": [
      {
        "id": "cmf3k2x9a0030abcd",
        "position": 0,
        "type": "email",
        "taskKind": null,
        "delayDays": 0,
        "subject": "Quick question, {{firstName}}",
        "body": "Hi {{firstName | there}},\n\nI put together a short demo of Acme: {{fields.demo_url}}\n\n{{senderName}}"
      },
      {
        "id": "cmf3k2x9a0031abcd",
        "position": 1,
        "type": "email",
        "taskKind": null,
        "delayDays": 3,
        "subject": "",
        "body": "Hi {{firstName | there}},\n\nJust following up on my last note.\n\n{{senderName}}"
      }
    ],
    "createdAt": "2026-09-01T09:00:00.000Z",
    "updatedAt": "2026-09-15T10:12:00.000Z"
  }
}

Update a campaign

PATCH/api/v1/campaigns/:id

Rename a campaign, change its sending settings, replace its steps, or change its status. Setting status to active starts sending on the runner's next tick and is refused until the campaign has a first email step with a subject and at least one mailbox.

Replacing steps re-numbers them. Members already past the end of a shorter sequence are marked completed.

Request body

FieldTypeDescription
namestring—
statusactive | paused | completed—
stepsobject[]Replaces every step.
timezonestring—
sendWindowStartnumber—
sendWindowEndnumber—
weekdaysOnlyboolean—
stopOnReplyboolean—
unsubscribeLinkboolean—

Responses

StatusMeaning
200Body: { campaign }.
404No campaign with that id.
422The campaign is not ready to be activated, or a step is invalid.

Example request

curl -X PATCH https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "paused"
  }'

Example response

{
  "campaign": {
    "id": "cmf3k2x9a0009abcd",
    "name": "Q4 outbound",
    "status": "paused",
    "timezone": "Europe/London",
    "sendWindowStart": 9,
    "sendWindowEnd": 17,
    "weekdaysOnly": true,
    "stopOnReply": true,
    "unsubscribeLink": true,
    "memberCount": 148,
    "mailboxes": [
      {
        "id": "cmf3k2x9a0020abcd",
        "name": "Outbound 1",
        "fromEmail": "jane@acme-mail.com",
        "active": true
      }
    ],
    "steps": [
      {
        "id": "cmf3k2x9a0030abcd",
        "position": 0,
        "type": "email",
        "taskKind": null,
        "delayDays": 0,
        "subject": "Quick question, {{firstName}}",
        "body": "Hi {{firstName | there}},\n\nI put together a short demo of Acme: {{fields.demo_url}}\n\n{{senderName}}"
      },
      {
        "id": "cmf3k2x9a0031abcd",
        "position": 1,
        "type": "email",
        "taskKind": null,
        "delayDays": 3,
        "subject": "",
        "body": "Hi {{firstName | there}},\n\nJust following up on my last note.\n\n{{senderName}}"
      }
    ],
    "createdAt": "2026-09-01T09:00:00.000Z",
    "updatedAt": "2026-09-15T10:12:00.000Z"
  }
}

List campaign members

GET/api/v1/campaigns/:id/members

The people enrolled in a campaign with their sequence state: which step is next, when it sends, and whether they replied. Status is active, replied, stopped, completed, bounced, blocked (dropped by a pre-send check) or unsubscribed.

Query parameters

ParameterTypeDescription
statusstringOnly members with this status.
repliedtrue | falsetrue for members who have replied.
limitnumberPage size, 1 to 100. Default 50.
cursorstringThe nextCursor from the previous page.

Example request

curl "https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd/members?replied=true" \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY"

Example response

{
  "members": [
    {
      "id": "cmf3k2x9a0010abcd",
      "status": "replied",
      "nextStep": 1,
      "nextSendAt": "2026-09-18T09:00:00.000Z",
      "lastSentAt": "2026-09-15T10:12:30.000Z",
      "repliedAt": "2026-09-16T11:00:00.000Z",
      "contact": {
        "id": "cmf3k2x9a0001abcd",
        "firstName": "Jane",
        "lastName": "Doe",
        "email": "jane@acme.com"
      },
      "createdAt": "2026-09-15T10:12:30.000Z"
    }
  ],
  "nextCursor": null
}

Add a contact to a campaign

POST/api/v1/campaigns/:id/members

Enrols one contact, identified by contactId or email. Idempotent: a contact already in the campaign is returned with outcome already_member and nothing changes.

Nothing is sent from this request. The new member is due immediately and the runner sends the first step on its next tick, inside the campaign's send window, from a mailbox with quota left. The campaign must be active for that to happen; campaignStatus in the response tells you.

Contacts on the do-not-email list are never added, and neither are contacts without an email address.

Request body

FieldTypeDescription
contactIdstringEither this or email is required.
emailstring—

Responses

StatusMeaning
201outcome: added. The member row is returned.
200outcome: already_member, suppressed, or no_email. member is the existing row or null.
404No campaign with that id, or no contact matched.

Example request

curl -X POST https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd/members \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@acme.com"
  }'

Example response

{
  "outcome": "added",
  "contactId": "cmf3k2x9a0001abcd",
  "campaignStatus": "active",
  "member": {
    "id": "cmf3k2x9a0010abcd",
    "status": "active",
    "nextStep": 0,
    "nextSendAt": "2026-09-15T10:12:30.000Z",
    "createdAt": "2026-09-15T10:12:30.000Z"
  }
}

Stop or resume a member

PATCH/api/v1/campaigns/:id/members/:memberId

Set a member's status to stopped to halt their sequence, or active to resume it from where it left off. A member with no steps left is marked completed instead.

Request body

FieldTypeDescription
statusrequiredactive | stopped—

Responses

StatusMeaning
200Body: { member }.
404No such campaign or member.

Example request

curl -X PATCH https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd/members/cmf3k2x9a0010abcd \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "stopped"
  }'

Example response

{
  "member": {
    "id": "cmf3k2x9a0010abcd",
    "status": "stopped",
    "nextStep": 1,
    "nextSendAt": null,
    "lastSentAt": "2026-09-15T10:12:30.000Z",
    "repliedAt": null,
    "contact": {
      "id": "cmf3k2x9a0001abcd",
      "firstName": "Jane",
      "lastName": "Doe",
      "email": "jane@acme.com"
    },
    "createdAt": "2026-09-15T10:12:30.000Z"
  }
}

Read a member's thread

GET/api/v1/campaigns/:id/members/:memberId/messages

Every email sent to and received from one campaign member, oldest first. direction is outbound or inbound. Use it to read replies before deciding what to do next.

Example request

curl "https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0001abcd/members/cmf3k2x9a0010abcd/messages" \
  -H "Authorization: Bearer $CROWDSTACK_API_KEY"

Example response

{
  "messages": [
    {
      "id": "cmf3k2x9a0130abcd",
      "direction": "outbound",
      "fromEmail": "jane@acme-mail.com",
      "toEmail": "jane@acme.com",
      "subject": "Quick question, Jane",
      "textBody": "Hi Jane, ...",
      "status": "sent",
      "occurredAt": "2026-09-15T10:12:30.000Z"
    },
    {
      "id": "cmf3k2x9a0131abcd",
      "direction": "inbound",
      "fromEmail": "jane@acme.com",
      "toEmail": "jane@acme-mail.com",
      "subject": "Re: Quick question, Jane",
      "textBody": "Sure, send it over.",
      "status": "received",
      "occurredAt": "2026-09-16T11:00:00.000Z"
    }
  ]
}