Sequences API

The Sequences API manages email sequences (multi-step drip / outbound automations) and the enrollments that drive contacts through them. Adding a contact and enrolling it is fully API-driven: once a contact is enrolled, the sequence processor sends each step automatically on schedule — no dashboard interaction required.

REST API. These are REST v1 endpoints authenticated with an API key, not tRPC. Send Authorization: Bearer rk_live_… and a JSON body. Successful responses are wrapped in { "data": … } (lists add a "meta" block). Generate a key under Settings → API Keys.

Required Scopes

ScopeGrants
sequences:readList/view sequences and enrollments
sequences:writeCreate/update/delete sequences; enroll, pause, resume, and unenroll contacts

sequences:write implies sequences:read.

Sequence Object

{
  id: string;                 // UUID
  organization_id: string;    // UUID
  name: string;
  description?: string | null;
  trigger_type: 'manual' | 'webhook' | 'form_submission' | 'tag_added' | 'contact_created';
  trigger_config: object;     // e.g. { webhook_id, tag, list_id }
  exit_conditions: object;    // e.g. { replied: true, unsubscribed: true }
  status: 'draft' | 'active' | 'paused' | 'archived';
  from_email?: string | null;
  from_name?: string | null;
  reply_to_email?: string | null;
  total_enrolled: number;
  total_completed: number;
  total_unsubscribed: number;
  created_at: string;         // ISO timestamp
  steps?: SequenceStep[];     // included on GET /sequences/:id
}

List Sequences

GET /api/v1/sequences

Scope: sequences:read

Query params: status (draft|active|paused|archived), limit (1-100, default 50), offset (default 0).

curl "https://crm.switchlabs.dev/api/v1/sequences?status=active&limit=20" \
  -H "Authorization: Bearer rk_live_xxx"

Response:

{
  "data": [ { "id": "uuid", "name": "Press Outreach", "status": "active", "total_enrolled": 42 } ],
  "meta": { "total": 3, "limit": 20, "offset": 0, "next_offset": null }
}

Create Sequence

POST /api/v1/sequences

Scope: sequences:write

Sequences are created as a draft. Add steps in the dashboard (or set them up server-side), then activate with PATCH … { "status": "active" }.

Body:

{
  name: string;                         // required, 1-200 chars
  description?: string;
  trigger_type?: 'manual' | 'webhook' | 'form_submission' | 'tag_added' | 'contact_created'; // default 'manual'
  trigger_config?: object;
  exit_conditions?: { replied?: boolean; converted?: boolean; unsubscribed?: boolean; manual?: boolean; bounced?: boolean };
  from_email?: string;
  from_name?: string;
  reply_to_email?: string;
}
curl -X POST "https://crm.switchlabs.dev/api/v1/sequences" \
  -H "Authorization: Bearer rk_live_xxx" -H "Content-Type: application/json" \
  -d '{ "name": "Press Outreach", "trigger_type": "manual" }'

Returns 201 with the created sequence.


Get / Update / Delete Sequence

GET /api/v1/sequences/:id — returns the sequence with its steps (ordered by position). Scope: sequences:read.

PATCH /api/v1/sequences/:id — update any of name, description, trigger_type, trigger_config, exit_conditions, from_email, from_name, reply_to_email, status. Scope: sequences:write.

Setting status to active requires the sequence to have at least one step (returns 400 otherwise) — the same rule as the dashboard.

DELETE /api/v1/sequences/:id — delete the sequence (cascades steps and enrollments). Returns 204. Scope: sequences:write.

curl -X PATCH "https://crm.switchlabs.dev/api/v1/sequences/SEQ_ID" \
  -H "Authorization: Bearer rk_live_xxx" -H "Content-Type: application/json" \
  -d '{ "status": "active" }'

Enroll a Contact

POST /api/v1/sequences/:id/enroll

Scope: sequences:write

Enroll one contact, identified by customer_id or email (resolved to an existing contact in your org). Applies the same eligibility guardrails as the dashboard "Enroll" action.

Body:

{
  customer_id?: string;   // UUID — provide this OR email
  email?: string;         // resolved to an existing contact
  enrollment_source?: string;    // default "api"
  enrollment_data?: object;      // arbitrary metadata stored on the enrollment
}
curl -X POST "https://crm.switchlabs.dev/api/v1/sequences/SEQ_ID/enroll" \
  -H "Authorization: Bearer rk_live_xxx" -H "Content-Type: application/json" \
  -d '{ "email": "reporter@example.com", "enrollment_source": "press_list" }'

Responses:

StatusMeaning
201Enrolled — body is the enrollment object
404Sequence or contact not found in your org
409Contact already enrolled in this sequence
422Contact ineligible for outreach — body error.details.reason explains why (contact_status:unsubscribed, converted_customer, active_ticket:open, …)

Bulk Enroll

POST /api/v1/sequences/:id/enroll_bulk

Scope: sequences:write

Enroll up to 1000 contacts in one call, by customer_ids and/or emails. Eligibility is applied per contact; ineligible / already-enrolled / unmatched contacts are skipped (not errors).

Body:

{
  customer_ids?: string[];   // UUIDs
  emails?: string[];         // resolved to existing contacts
  enrollment_source?: string;  // default "api_bulk"
}
curl -X POST "https://crm.switchlabs.dev/api/v1/sequences/SEQ_ID/enroll_bulk" \
  -H "Authorization: Bearer rk_live_xxx" -H "Content-Type: application/json" \
  -d '{ "emails": ["a@example.com", "b@example.com"] }'

Response:

{
  "data": {
    "enrolled": 1,
    "skipped": 1,
    "results": [
      { "customerId": "uuid", "status": "enrolled" },
      { "email": "b@example.com", "status": "skipped", "code": "customer_not_found", "reason": "No contact found with this email" }
    ]
  }
}

List Enrollments

GET /api/v1/sequences/:id/enrollments

Scope: sequences:read

Query params: status (active|paused|completed|exited|failed), limit, offset. Each row embeds a customer summary.


Pause / Resume / Unenroll

PATCH /api/v1/sequences/:id/enrollments/:enrollmentId — set { "status": "paused" } to pause or { "status": "active" } to resume. Scope: sequences:write.

DELETE /api/v1/sequences/:id/enrollments/:enrollmentId — unenroll (exit). Optional ?reason= (replied|converted|unsubscribed|manual|bounced, default manual). Cancels any pending drafts for the enrollment. Scope: sequences:write.

curl -X DELETE "https://crm.switchlabs.dev/api/v1/sequences/SEQ_ID/enrollments/ENR_ID?reason=replied" \
  -H "Authorization: Bearer rk_live_xxx"

Auto-Enrollment via the Customers API

Creating a contact through POST /api/v1/customers automatically enrolls it into any active sequence whose trigger_type is contact_created, and (when tags are supplied) any tag_added-triggered sequence. This is the lowest-friction way to "add a lead and start outreach" in a single call — no explicit enroll needed.


How Sending Works

Enrollment only schedules the first step (next_send_at). The sequence-processor cron sends each step on schedule and advances the enrollment automatically. You never call a "send" endpoint for sequences — enroll and the engine takes over.

See also: Campaigns · Marketing Lists · Customers