List campaigns

GET/api/v1/campaigns

Every campaign in the workspace, most recently updated first, with member and step counts. Returns up to 200.

Responses

StatusMeaning
200Body: { campaigns: [...] }.

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 subjects and bodies are the raw templates with merge fields intact; an empty subject on a later email step means it replies in the existing thread.

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 (linkedin_view, linkedin_connect, linkedin_message, linkedin_post, call or custom). delayDays is the wait after the previous step, 0 on the first.

Responses

StatusMeaning
200Body: { campaign }.
404No campaign with that id in this workspace.

Example request

curl https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0009abcd \
  -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"
  }
}

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, whatever their sequence state.

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
contactIdstringThe contact's id. Either this or email is required.
emailstringThe contact's email. Either this or contactId is required.

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. Create the contact first.

Example request

curl -X POST https://crowdstack.org/api/v1/campaigns/cmf3k2x9a0009abcd/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"
  }
}