> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sequenzy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Enroll Audience

> Enroll everyone, lists, or a segment in a sequence as a background run

Enroll a whole audience in a sequence. Unlike [Enroll Subscribers](/api-reference/sequences/enroll), there is no 500-contact cap: Sequenzy starts a background run that walks the audience in batches and reports progress. This is how a manual-trigger countdown sequence, such as a Black Friday countdown, gets its audience: enable the sequence, then enroll the audience.

* Only active contacts with an email address are enrolled. Unsubscribed and bounced contacts are never enrolled.
* Contacts already active or waiting in the sequence are skipped. Sequences with `enrollmentMode: "one_time"` also skip contacts who completed or were cancelled before.
* Late enrollees in a sequence with key-date waits skip the steps whose moment already passed and continue from the next wait (the step's `pastAction`).
* Only one run per sequence can be queued or running at a time.
* The sequence must be enabled and accepting entrants.

Use `POST /api/v1/sequences/{sequenceId}/enroll-audience/estimate` with the same `audience` body to preview the counts without starting a run.

## Request

<ParamField path="sequenceId" type="string" required>
  Sequence ID.
</ParamField>

<ParamField body="audience" type="object" required>
  Who to enroll. Same shape as campaign `targetLists`.

  <Expandable title="Audience shapes">
    <ParamField body="type" type="string" required>
      `all`, `lists`, `segment`, `filtered`, or `rules`.
    </ParamField>

    <ParamField body="listIds" type="string[]">
      For `lists`: members of any of these lists.
    </ParamField>

    <ParamField body="segmentId" type="string">
      For `segment`: members of this saved segment.
    </ParamField>

    <ParamField body="filters" type="object[]">
      For `filtered`: subscriber filters, with an optional `filterJoinOperator` of `and` or `or`.
    </ParamField>

    <ParamField body="include" type="object[]">
      For `rules`: include rules of type `all`, `lists`, `segments`, or `filtered`.
    </ParamField>

    <ParamField body="exclude" type="object[]">
      For `rules`: exclude rules of type `lists`, `segments`, or `filtered`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="targetNodeId" type="string | null">
  Step to start contacts at. Defaults to the first step after the trigger.
  Cannot be a trigger node.
</ParamField>

<ParamField body="scheduledFor" type="string | null">
  ISO 8601 date-time to start the run later, up to one year ahead. The run is
  created with `status: "queued"` and `scheduledFor` set, and can be cancelled
  before it starts. A past value starts now.
</ParamField>

To keep enrolling people who join the audience after this run (new list
members, new segment matches) until the countdown ends, set
`audienceAutoEnroll` on the sequence with [Update Sequence](/api-reference/sequences/update).

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences/seq_abc123/enroll-audience" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audience": {"type": "segment", "segmentId": "seg_past_buyers"}}'
```

Estimate first:

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences/seq_abc123/enroll-audience/estimate" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"audience": {"type": "all"}}'
```

## Responses

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "audienceEnrollment": {
      "id": "aer_abc123",
      "sequenceId": "seq_abc123",
      "status": "queued",
      "audience": { "type": "segment", "segmentId": "seg_past_buyers" },
      "targetNodeId": "node_delay_1",
      "estimatedCount": 11800,
      "processedCount": 0,
      "enrolledCount": 0,
      "skippedCount": 0,
      "source": "api",
      "error": null,
      "createdAt": "2026-11-01T10:00:00.000Z",
      "scheduledFor": null,
      "startedAt": null,
      "completedAt": null,
      "cancelRequestedAt": null
    },
    "message": "Enrolling about 11,800 contacts in the background. Poll GET /sequences/seq_abc123/audience-enrollments/aer_abc123 for progress."
  }
  ```

  ```json 200 (estimate) theme={null}
  {
    "success": true,
    "matchingCount": 12000,
    "alreadyEnrolledCount": 200,
    "enrollableCount": 11800
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Activate the sequence before enrolling an audience."
  }
  ```

  ```json 400 theme={null}
  {
    "error": "The target step does not belong to this sequence"
  }
  ```

  ```json 401 theme={null}
  {
    "error": "Invalid API key"
  }
  ```

  ```json 404 theme={null}
  {
    "error": "Sequence not found"
  }
  ```

  ```json 409 theme={null}
  {
    "success": false,
    "code": "run_in_progress",
    "error": "An audience enrollment is already in progress for this sequence. Wait for it to finish or cancel it first."
  }
  ```
</ResponseExample>

## Failure and retry semantics

The run is idempotent per contact: the cursor is committed together with every batch of enrollments, so a retried worker resumes where it stopped and never enrolls the same contact twice. If the run fails after retries, its `status` becomes `failed` with an `error`, and the contacts enrolled before the failure stay in the sequence. Cancelling stops after the current batch and keeps everyone already enrolled.
