---
title: "Enroll leads in a sequence"
method: POST
path: "/v1/enrollments"
tags: ["Sequences"]
---

# Enroll leads in a sequence

`POST /v1/enrollments`

## Request

- Base URL: `https://api.automatedclientacquisition.com`
- URL: `https://api.automatedclientacquisition.com/v1/enrollments`
- Auth: HTTP bearer

Enrolls contacts into an email sequence. Contacts are resolved to lead records
automatically — you do not create leads directly. Send times are staggered
across the sequence's sending window rather than fired at once.

Provide exactly one audience: `contactIds`, `listId`, or `listIds`.

> **Leads already active in another sequence are skipped, not enrolled.** This
> is deliberate — it stops the same person receiving two campaigns at once.
> They are reported in `skippedActiveElsewhere`. Set `allowConcurrent: true`
> only if you genuinely want overlapping campaigns.

Contacts without a valid email come back in `noEmail`; unsubscribed or
suppressed contacts in `suppressed`. Neither is an error.

**Very large audiences:** enrolling tens of thousands of leads in one call can
exceed the proxy timeout. Enroll by `listId` in batches, or split the audience
across calls.

## Request body

- object
  - `sequenceId` string, uuid, required: Sequence to enroll into.
  - `contactIds` string[]: Enroll specific contacts.
  - `listId` string, uuid: Enroll every member of one lead list.
  - `listIds` string[]: Enroll the union of several lead lists.
  - `delayHours` number: Hold the first email for this many hours.
  - `allowConcurrent` boolean: Opt out of one-active-sequence-per-lead.
  - `customDataByContactId` object: Per-contact merge-variable overrides, keyed by **crm_contact id** (not lead id), written to the enrollment's `custom_data`. Use `custom_email_N_subject` / `custom_email_N_body` (N is 1-indexed per step) to give each lead its own subject and body, resolving `{{email_subject_N}}` / `{{email_body_N}}`. This is how per-lead AI-written sequences are delivered. Re-enrolling a contact that is already enrolled keeps the existing `custom_data` rather than overwriting it.

## Response `200`

Enrollment processed. Skips are normal, not failures.

- object
  - `enrolled` integer: Leads enrolled.
  - `skipped` integer: Already enrolled in this sequence.
  - `noEmail` integer: No valid email address.
  - `suppressed` integer: Unsubscribed, bounced or suppressed.
  - `skippedActiveElsewhere` integer: Active in another sequence, so left alone.

## Other responses

- `400`: Malformed or invalid body. Do not retry — it will fail identically.
- `401`: Missing, invalid or revoked token.
- `403`: The resource belongs to another organization.
- `500`: Something broke on our side. Safe to retry with backoff.

---

[API](https://skmtc.dev/automatedclientacquisition/apis/aca-api.md) · [All operations](https://skmtc.dev/automatedclientacquisition/apis/aca-api/llms.txt) · [OpenAPI document](https://skmtc.dev/automatedclientacquisition/apis/aca-api/revisions/8f3e3b141311?raw)
