# Sequences

Send a series of timed messages to contacts who opted in, with automatic exits when they reply or unsubscribe, on WhatsApp, Instagram, Facebook and more.

A sequence is a drip campaign over DMs: a list of steps, each with a delay and a message, sent to every contact you enroll. Typical uses are onboarding after a sign-up, a follow-up series after a lead form, or a reminder before an appointment. Contacts leave automatically when they reply or unsubscribe, so a person who starts talking to you stops getting scripted messages.

Sequences run on one account: WhatsApp, Instagram, Facebook, X, Bluesky or Slack. Telegram is coming soon.

## Create a sequence

```bash cURL
curl -X POST https://api.pozy.ai/v1/sequences \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profileId": "6650c1e2a3b4c5d6e7f80911",
    "accountId": "6650c1e2a3b4c5d6e7f80a44",
    "platform": "whatsapp",
    "name": "Trial onboarding",
    "steps": [
      { "order": 1, "delayMinutes": 0, "template": { "name": "trial_welcome", "language": "en", "variableMapping": { "1": { "field": "name" } } } },
      { "order": 2, "delayMinutes": 1440, "template": { "name": "trial_day_two_tips", "language": "en" } },
      { "order": 3, "delayMinutes": 7200, "template": { "name": "trial_ending", "language": "en" } }
    ],
    "exitOnReply": true,
    "exitOnUnsubscribe": true
  }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/sequences", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    profileId: "6650c1e2a3b4c5d6e7f80911",
    accountId: "6650c1e2a3b4c5d6e7f80a44",
    platform: "whatsapp",
    name: "Trial onboarding",
    steps: [
      { order: 1, delayMinutes: 0, template: { name: "trial_welcome", language: "en", variableMapping: { "1": { field: "name" } } } },
      { order: 2, delayMinutes: 1440, template: { name: "trial_day_two_tips", language: "en" } },
      { order: 3, delayMinutes: 7200, template: { name: "trial_ending", language: "en" } },
    ],
    exitOnReply: true,
    exitOnUnsubscribe: true,
  }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);

const res2 = await fetch(`https://api.pozy.ai/v1/sequences/${data.sequence.id}/activate`, {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
if (!res2.ok) throw new Error((await res2.json()).error);
```
```python Python
import os
import requests

H = {"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"}
seq = requests.post(
    "https://api.pozy.ai/v1/sequences",
    headers=H,
    json={
        "profileId": "6650c1e2a3b4c5d6e7f80911",
        "accountId": "6650c1e2a3b4c5d6e7f80a44",
        "platform": "whatsapp",
        "name": "Trial onboarding",
        "steps": [
            {"order": 1, "delayMinutes": 0, "template": {"name": "trial_welcome", "language": "en"}},
            {"order": 2, "delayMinutes": 1440, "template": {"name": "trial_day_two_tips", "language": "en"}},
        ],
    },
    timeout=30,
).json()["sequence"]
requests.post(f"https://api.pozy.ai/v1/sequences/{seq['id']}/activate", headers=H, timeout=30)
```

A sequence starts as a `draft`. Activate it to start sending; it needs at least one step.

### Steps

| Field | Notes |
| --- | --- |
| `order` | Position of the step, starting at 1 |
| `delayMinutes` | How long to wait before sending this step. `0` on the first step sends it as soon as the contact is enrolled |
| `message` | `{ text }` for a free-form message |
| `template` | `{ name, language, variableMapping }` for an approved WhatsApp template |

Check `nextStepAt` on each enrollment to see exactly when the next step goes out.

### Pick message or template per platform

- **WhatsApp:** a step that may go out more than 24 hours after the contact last wrote must be an approved template. In practice, use templates for every step. `variableMapping` maps template placeholders to `name`, `phone`, `email`, `company` or a fixed `custom` value. See [Templates](https://pozy.ai/docs/whatsapp/templates).
- **Instagram and Facebook:** automated messages can only be sent within 24 hours of the contact's last message, so a long sequence stops reaching people who went quiet. Keep sequences short there, or use them right after a conversation starts.
- **Telegram, X, Bluesky, Slack:** `message.text` steps.

## Enroll contacts

```bash cURL
curl -X POST https://api.pozy.ai/v1/sequences/6650c1e2a3b4c5d6e7f80f77/enroll \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["6650c1e2a3b4c5d6e7f80c44", "6650c1e2a3b4c5d6e7f80c45"] }'
```
```ts Node
const res = await fetch("https://api.pozy.ai/v1/sequences/6650c1e2a3b4c5d6e7f80f77/enroll", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.POZY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ contactIds: ["6650c1e2a3b4c5d6e7f80c44", "6650c1e2a3b4c5d6e7f80c45"] }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
console.log(data.enrolled, data.failed, data.results);
```
```python Python
resp = requests.post(
    "https://api.pozy.ai/v1/sequences/6650c1e2a3b4c5d6e7f80f77/enroll",
    headers=H,
    json={"contactIds": ["6650c1e2a3b4c5d6e7f80c44", "6650c1e2a3b4c5d6e7f80c45"]},
    timeout=30,
)
print(resp.json())
```

Pozy picks each contact's channel on the sequence's account; pass `channelIds` to choose it yourself. Contacts already enrolled are skipped. The response lists a result per contact, so check `failed` and the `results` array.

Other ways to enroll:

- A workflow's [`enroll_sequence`](https://pozy.ai/docs/automations/workflow-nodes#enroll-sequence) node enrolls the contact in the middle of a conversation, for example after they share an email.
- Your backend can enroll contacts when something happens in your product, such as a trial starting.

Only enroll people who agreed to hear from you. On WhatsApp and SMS, that consent is a platform rule, not just good practice.

## How contacts leave

| `exitReason` | Why |
| --- | --- |
| `completed` | The last step was sent |
| `replied` | The contact replied, and `exitOnReply` is on (the default) |
| `unsubscribed` | The contact opted out, and `exitOnUnsubscribe` is on (the default) |
| `manual` | You removed them with `DELETE /v1/sequences/{id}/enroll/{contactId}` |
| `failed` | A step kept failing to send |

`sequence.enrolled` and `sequence.exited` webhooks report each move, with `exitReason` on exits.

## Monitor and change

- `GET /v1/sequences/{id}` returns the steps and totals (`totalEnrolled`, `totalCompleted`, `totalExited`).
- `GET /v1/sequences/{id}/enrollments` lists contacts with `status` (`active`, `completed`, `exited`, `paused`), `currentStepIndex`, `stepsSent`, `lastStepSentAt` and `nextStepAt`.
- `POST /v1/sequences/{id}/pause` stops all sends until you activate it again. Enrolled contacts keep their place.
- `PATCH /v1/sequences/{id}` changes the name, exits or steps. The step list is replaced as a whole and can only change while the sequence is `draft` or `paused`.
- `DELETE /v1/sequences/{id}` deletes the sequence and stops every active enrollment.

Messages a sequence sends arrive as `message.sent` with `sentVia: "sequence"` and the `sequenceId`, and count toward your outbound messages.

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/sequences`](https://pozy.ai/docs/api/listSequences) | List sequences |
| [`POST /v1/sequences`](https://pozy.ai/docs/api/createSequence) | Create sequence |
| [`GET /v1/sequences/{sequenceId}`](https://pozy.ai/docs/api/getSequence) | Get sequence with steps |
| [`PATCH /v1/sequences/{sequenceId}`](https://pozy.ai/docs/api/updateSequence) | Update sequence |
| [`DELETE /v1/sequences/{sequenceId}`](https://pozy.ai/docs/api/deleteSequence) | Delete sequence |
| [`POST /v1/sequences/{sequenceId}/activate`](https://pozy.ai/docs/api/activateSequence) | Activate sequence |
| [`POST /v1/sequences/{sequenceId}/pause`](https://pozy.ai/docs/api/pauseSequence) | Pause sequence |
| [`POST /v1/sequences/{sequenceId}/enroll`](https://pozy.ai/docs/api/enrollContacts) | Enroll contacts in a sequence |
| [`DELETE /v1/sequences/{sequenceId}/enroll/{contactId}`](https://pozy.ai/docs/api/unenrollContact) | Unenroll contact |
| [`GET /v1/sequences/{sequenceId}/enrollments`](https://pozy.ai/docs/api/listSequenceEnrollments) | List enrollments for a sequence |

## Webhook events

| Event | When it fires |
| --- | --- |
| [`sequence.enrolled`](https://pozy.ai/docs/webhooks/events#sequence-enrolled) | Sequence enrolled. |
| [`sequence.exited`](https://pozy.ai/docs/webhooks/events#sequence-exited) | Sequence exited. |
