API / Campaigns
Campaigns
Outreach sequences. List them to find an id, then enrol contacts. Sending itself stays with the campaign runner, so send windows, mailbox limits and unsubscribes apply to API-enrolled contacts exactly as they do to everyone else.
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
| Status | Meaning |
|---|---|
| 200 | Body: { 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
| Status | Meaning |
|---|---|
| 200 | Body: { campaign }. |
| 404 | No 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
| Field | Type | Description |
|---|---|---|
contactId | string | The contact's id. Either this or email is required. |
email | string | The contact's email. Either this or contactId is required. |
Responses
| Status | Meaning |
|---|---|
| 201 | outcome: added. The member row is returned. |
| 200 | outcome: already_member, suppressed, or no_email. member is the existing row or null. |
| 404 | No 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"
}
}