# Add a person to a sequence

> Starts the sequence for one person, by to (added as a contact when new) or contact_id.

`POST /v1/sequences/{id}/enrollments`

Starts the sequence for one person, by `to` (added as a contact when new) or `contact_id`. A person can be in a sequence once at a time: adding somebody already in it answers `conflict`, and so does adding somebody on your stop list or a sequence that is archived.

Needs a key with the `messages:send` scope. [Keys and scopes](https://usebluebot.com/docs/concepts/api-keys)

Send an `Idempotency-Key` so a retry never acts twice. [Idempotency](https://usebluebot.com/docs/concepts/idempotency)

## Path parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | The sequence’s id. Starts with `seq_` |

## Headers

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `Idempotency-Key` | string | No | Any string of up to 255 characters, new for each request. A retry with the same key gets the first answer back for 24 hours, with `Idempotent-Replayed: true`. Up to 255 characters |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `to` | string | No | The person to add. Send `to` or `contact_id`. 3 to 320 characters |
| `contact_id` | string | No | A ct_ id. Starts with `ct_` |
| `first_name` | string | No | Saved on the contact when it has no first name yet. Up to 100 characters |
| `last_name` | string | No | Up to 100 characters |
| `variables` | map of string | No | Values for the steps’ `{{variables}}`. |

## Example

```bash
curl -X POST "https://usebluebot.com/api/v1/sequences/seq_1Hq2LxW9cVbN3mKpRtYs0a/enrollments" \
  -H "Authorization: Bearer $BLUEBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70" \
  -d '{
    "to": "+15555550123",
    "first_name": "Maria",
    "variables": {
      "listing": "412 Oak St"
    }
  }'
```

```ts
import BlueBot from '@usebluebot/sdk';

const client = new BlueBot({ apiKey: process.env.BLUEBOT_API_KEY });

const enrollment = await client.sequences.enrollments.create('seq_1Hq2LxW9cVbN3mKpRtYs0a', {
  to: '+15555550123',
  firstName: 'Maria',
  variables: {
    listing: '412 Oak St',
  },
}, { idempotencyKey: '0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70' });
console.log(enrollment);
```

```python
import os
from bluebot import BlueBot

client = BlueBot(api_key=os.environ["BLUEBOT_API_KEY"])

enrollment = client.sequences.enrollments.create(
    "seq_1Hq2LxW9cVbN3mKpRtYs0a",
    to="+15555550123",
    first_name="Maria",
    variables={
        "listing": "412 Oak St",
    },
    idempotency_key="0f6d8c2e-5b1a-4c7e-9d3f-2a8b6e1c4d70",
)
print(enrollment)
```

## Response

`201`: Their run.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `id` | string | Yes | A enr_ id. Starts with `enr_` |
| `object` | "enrollment" | Yes |   |
| `sequence_id` | string | Yes | A seq_ id. Starts with `seq_` |
| `contact_id` | string | Yes | A ct_ id. Starts with `ct_` |
| `handle` | string | Yes | A phone number in E.164 (`+15555550123`) or an email address an Apple ID uses (iMessage only). |
| `state` | string | Yes | One of `active`, `completed`, `stopped`, `canceled` |
| `current_step` | integer | Yes | The position of the next step. |
| `next_run_at` | timestamp or null | Yes | When the next step happens, while active. |
| `stop_reason` | string or null | Yes | Why it ended early, when it did. One of `replied`, `stopped`, `canceled`, `sequence_archived`, `failed` |
| `enrolled_via` | string | Yes | One of `dashboard`, `api`, `zapier`, `make`, `n8n`, `pipedream`, `cli`, `hook`, `keyword`, `mcp`, `assistant`, `widget` |
| `variables` | map of string | Yes | Values for the steps’ `{{variables}}`. |
| `enrolled_at` | timestamp | Yes | When the person was added. |
| `completed_at` | timestamp or null | Yes | When it ended, by finishing or stopping. |
| `timeline` | array of object | No | What happened, oldest first. Only on `GET /v1/enrollments/{id}`. |
| `timeline[].type` | string | Yes | One of `enrolled`, `text_queued`, `text_skipped`, `waiting`, `window`, `stopped`, `completed`, `canceled` |
| `timeline[].position` | integer or null | Yes | The step it is about. |
| `timeline[].message_id` | string or null | Yes | A msg_ id. Starts with `msg_` |
| `timeline[].until` | timestamp or null | Yes | For a wait, when it ends. |
| `timeline[].detail` | string or null | Yes | What happened, in words. |
| `timeline[].at` | timestamp | Yes | When. |

The answer, `201`:

```json
{
  "id": "enr_1Hq2LxW9cVbN3mKpRtYs0a",
  "object": "enrollment",
  "sequence_id": "seq_1Hq2LxW9cVbN3mKpRtYs0a",
  "contact_id": "ct_1Hq2LxW9cVbN3mKpRtYs0a",
  "handle": "+15555550123",
  "state": "active",
  "current_step": 0,
  "next_run_at": "2026-11-13T19:41:00.000Z",
  "stop_reason": null,
  "enrolled_via": "api",
  "variables": {
    "listing": "412 Oak St"
  },
  "enrolled_at": "2026-11-13T19:41:00.000Z",
  "completed_at": null
}
```

## Errors

Every refusal has the same envelope and a page of its own. These are the ones this operation can give.

| Code                                                        | Status | What it means                                                                                                                                    |
| ----------------------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| [`unauthorized`](https://usebluebot.com/docs/errors/unauthorized)                 | 401    | The request carried no key, or a key BlueBot doesn’t know: mistyped, revoked, or a test key sent to the live API.                                |
| [`insufficient_scope`](https://usebluebot.com/docs/errors/insufficient_scope)     | 403    | The key doesn’t carry the scope the endpoint needs. Each endpoint’s reference names its scope.                                                   |
| [`rate_limited`](https://usebluebot.com/docs/errors/rate_limited)                 | 429    | Too many requests. A key may make 120 requests a minute. The `RateLimit` header says how many are left, and `Retry-After` how long to wait.      |
| [`internal`](https://usebluebot.com/docs/errors/internal)                         | 500    | Something went wrong on BlueBot’s side. It was reported. Send the request again; with an Idempotency-Key that is always safe.                    |
| [`invalid_request`](https://usebluebot.com/docs/errors/invalid_request)           | 400    | Something in the request doesn’t fit: a missing or malformed field, a bad id, or a combination the endpoint refuses. `details` names each field. |
| [`not_found`](https://usebluebot.com/docs/errors/not_found)                       | 404    | There is no such thing on this account. Ids are prefixed by kind, so a message id starts with `msg_`.                                            |
| [`conflict`](https://usebluebot.com/docs/errors/conflict)                         | 409    | The request doesn’t fit the thing’s current state, or another request with the same Idempotency-Key is still running.                            |
| [`plan_lapsed`](https://usebluebot.com/docs/errors/plan_lapsed)                   | 402    | The account’s plan isn’t active, so BlueBot isn’t taking new texts. Texts already queued wait and go once the plan is active again.              |
| [`idempotency_conflict`](https://usebluebot.com/docs/errors/idempotency_conflict) | 409    | The Idempotency-Key was used in the last 24 hours with a different request. A key stands for one request.                                        |

## Try it
