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

# Sequences

> Upsert a contact and start a personalized outbound sequence in one call

<Info>
  **Plan availability:** Standard or higher (requires both **Sequences** and **API Access**). See [Plans & Billing](/features/settings/plans).
</Info>

The Sequences API lets you trigger a fully personalized outbound campaign for a single contact from your own backend. In one call, Clarky will:

1. **Upsert the contact** in your workspace (matched by primary email, falling back to phone).
2. **Append the notes** you send (`Speaks Spanish. Located in Madrid.`) to the contact's record so they survive across calls.
3. **Generate a bespoke sequence** from a saved template using a two-stage LLM pipeline — emails are written for the specific recipient, in their language, referencing real context. SMS / voice scripts are generated the same way.
4. **Optionally launch immediately** — with `auto_approve: true`, the first step (call or email) fires within seconds of generation completing, no human review required.

Templates encode which channels are allowed (`email`, `voice`, `sms`), so the API itself does not expose a channel selector — pick a template that uses the channels you want.

<Note>
  **Generation takes time.** The endpoint responds with `202 Accepted` after a few hundred milliseconds, but the LLM pipeline runs in the background and takes \~30–90s before the first outbound action goes out. Poll the status endpoint to know when it has shipped.
</Note>

## Start a sequence

<ParamField path="method" type="POST">
  `POST /api/v1/sequences/start`
</ParamField>

### Request body

<ParamField body="template_id" type="string" required>
  ID of a [sequence template](#) in your workspace. The template defines the goal prompt, allowed channels, pacing, and (optionally) the bot identity.
</ParamField>

<ParamField body="bot_id" type="string">
  Required only if the template has no `bot_id`. The bot whose email/phone identity will be used as the sender.
</ParamField>

<ParamField body="contact" type="object" required>
  The contact to upsert.

  <Expandable title="Properties">
    <ResponseField name="emails" type="string[]">
      Array of email addresses. The first is treated as primary and used for matching.
    </ResponseField>

    <ResponseField name="phone_numbers" type="string[]">
      Array of phone numbers in E.164 format. The first is primary. Used for matching when no email is provided.
    </ResponseField>

    <ResponseField name="first_name" type="string" />

    <ResponseField name="last_name" type="string" />

    <ResponseField name="company" type="string" />

    <ResponseField name="job_title" type="string" />

    <ResponseField name="notes" type="string">
      Free-text context the LLM will use to personalize the outreach — e.g. `"Speaks Spanish. Located in Madrid. Returning lead."`. On an existing contact, new notes are **appended** (not overwritten) with a timestamp tag like `[via API 2026-05-06]`.
    </ResponseField>

    <ResponseField name="custom_fields" type="object">
      Workspace-defined custom fields, stored as JSON.
    </ResponseField>

    <ResponseField name="tags" type="string[]" />

    <ResponseField name="lead_source" type="string" />
  </Expandable>

  At least one of `emails` or `phone_numbers` is required.
</ParamField>

<ParamField body="auto_approve" type="boolean" default="false">
  When `true`, skips the human review gate. After generation, step 1's `scheduled_for` is forced to `now` and the dispatcher fires immediately — the contact's phone rings (or inbox dings) within seconds. Use this for trusted automation.

  When `false` (the default), the sequence is generated and parked in `pending_approval`. Workspace admins receive an email with a review link.
</ParamField>

<ParamField body="idempotency_key" type="string">
  A stable identifier (e.g. your internal lead ID). Calling the endpoint a second time with the same key returns the original `sequence_id` and `enrollment_id` — no duplicate sequence is created.
</ParamField>

<ParamField body="name" type="string">
  Display name for the sequence. Defaults to the template's name.
</ParamField>

### Response — 202 Accepted

```json theme={null}
{
  "data": {
    "sequence_id":    "seq_01HXYZ...",
    "enrollment_id":  "enr_01HXYZ...",
    "contact_id":     "c_01HXYZ...",
    "contact_action": "created",
    "auto_approve":   true,
    "status":         "generating",
    "status_url":     "/api/v1/sequences/enrollments/enr_01HXYZ..."
  }
}
```

### Example — call back a Spanish-speaking lead immediately

```bash theme={null}
curl -X POST "https://clarky.ai/api/v1/sequences/start" \
  -H "Authorization: Bearer ck_live_example" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id":     "tpl_01HABC...",
    "contact": {
      "first_name":    "Maria",
      "last_name":     "García",
      "emails":        ["maria@example.com"],
      "phone_numbers": ["+34911234567"],
      "company":       "Acme Spain",
      "notes":         "Speaks Spanish. Located in Madrid. Submitted contact form 30 seconds ago asking about pricing."
    },
    "auto_approve":    true,
    "idempotency_key": "lead_20260506_84211"
  }'
```

### Errors

| Code  | Meaning                                                                                           |
| ----- | ------------------------------------------------------------------------------------------------- |
| `400` | Missing required field, or template has no `bot_id` and none was provided.                        |
| `401` | Invalid or revoked API key.                                                                       |
| `403` | API key does not have `write` scope.                                                              |
| `404` | Template not found in this workspace.                                                             |
| `422` | Validation error — e.g. contact has no email or phone, or `bot_id` is from a different workspace. |
| `429` | Rate limit exceeded.                                                                              |

***

## Get enrollment status

<ParamField path="method" type="GET">
  `GET /api/v1/sequences/enrollments/{enrollment_id}`
</ParamField>

Use this to poll for generation progress, see the generated steps, and check delivery state.

### Status values

| Status                 | Meaning                                                                                                        |
| ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `generating`           | LLM is still producing the sequence. Typical 30–90s.                                                           |
| `pending_approval`     | Sequence is generated but awaiting human approval (only when `auto_approve: false`). Admins have been emailed. |
| `approved`             | Steps are scheduled. The first step is in flight or already sent.                                              |
| `running`              | Sequence is active across multiple steps.                                                                      |
| `stopped_reply`        | Contact replied; the sequence halted (if `stop_on_reply` is on for the template).                              |
| `stopped_unsubscribed` | Contact's email/phone is suppressed.                                                                           |
| `stopped_user`         | Manually halted by a workspace user.                                                                           |
| `completed`            | All steps sent.                                                                                                |
| `failed`               | Generation or dispatch failed. See `generation_error`.                                                         |

### Example response

```json theme={null}
{
  "data": {
    "id":           "enr_01HXYZ...",
    "sequence_id":  "seq_01HXYZ...",
    "contact_id":   "c_01HXYZ...",
    "status":       "approved",
    "generation_error": null,
    "approved_at":  "2026-05-06T14:22:11Z",
    "stop_reason":  null,
    "created_at":   "2026-05-06T14:21:14Z",
    "updated_at":   "2026-05-06T14:22:11Z",
    "sequence": {
      "id":          "seq_01HXYZ...",
      "name":        "Inbound lead — Spanish",
      "status":      "active",
      "bot_id":      "b_01HDEF...",
      "template_id": "tpl_01HABC..."
    },
    "steps": [
      {
        "id":            "stp_01...",
        "sort_order":    1,
        "channel":       "voice",
        "status":        "sent",
        "scheduled_for": "2026-05-06T14:22:11Z",
        "sent_at":       "2026-05-06T14:22:18Z",
        "to_identifier": "+34911234567",
        "voice_objective": "Confirm pricing tier and book a 15-minute demo this week",
        "voice_script":    "Hola Maria, te llamo desde Acme..."
      },
      {
        "id":            "stp_02...",
        "sort_order":    2,
        "channel":       "email",
        "status":        "scheduled",
        "scheduled_for": "2026-05-07T15:00:00Z",
        "email_subject": "Resumen de la llamada y próximos pasos"
      }
    ]
  }
}
```

***

## Notes & limits

* **Latency floor:** \~30–90s from API call to first outbound action. Two LLM calls run in the background; this can't be made faster while still producing language-matched, personalized content.
* **Channels are template-driven.** If the template's `allowed_channels` is `["email", "voice"]`, no SMS will be sent regardless of what the contact has on file.
* **Concurrent enrollments are allowed** — calling the endpoint while the contact is in another active sequence will not be rejected.
* **Suppressed contacts** (unsubscribed email, opted-out phone) cause the enrollment to fail with `no_usable_channels`. Polling the status endpoint will reflect this.
