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

# Run a sequence over the API

> Build a cadence, add contacts, and work the task queue programmatically, end to end.

This walks the whole loop: create a sequence, give it steps, preview who would go in, enrol them,
then read and resolve the tasks it produces. Every call uses the `x-api-key` header and the base URL
`https://client-api.salesfinity.co`.

<Note>
  `/v2/sequencer/*` is the Salesfinity Sequencer. The older `GET /v1/sequences` is a different
  thing entirely — it lists sequence *names* seen on your call logs from an external CRM. It is
  unchanged and unrelated.
</Note>

## Before you start: who is acting

An API key identifies a **team**, not a person. The Sequencer records a person on everything it
writes — a sequence has an author, an enrollment has an owner, a task has an assignee — so every
write takes a `user_id`, which must be an active member of the key's team.

Some endpoints also take `owner_id` or `assignee_id`. The distinction matters:

| Field         | Means                                                              |
| ------------- | ------------------------------------------------------------------ |
| `user_id`     | who is performing the action                                       |
| `owner_id`    | who ends up holding the enrollment, and whose queue gets its tasks |
| `assignee_id` | who ends up holding the task                                       |

`owner_id` and `assignee_id` default to `user_id` when omitted.

## 1. Create the sequence

```bash theme={null}
curl -X POST https://client-api.salesfinity.co/v2/sequencer/sequences \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{
    "user_id": "507f1f77bcf86cd799439011",
    "name": "Q3 outbound",
    "schedule": { "days_of_week": [1,2,3,4,5], "start_hour": 8, "end_hour": 17 }
  }'
```

It starts disabled and has no steps. A sequence with no steps cannot be enrolled into.

## 2. Add steps

Steps run in the order you create them. `interval_seconds` is the delay from the previous step — or
from enrolment, for the first one.

```bash theme={null}
# Day 0: a call task for the rep who owns the enrollment
curl -X POST https://client-api.salesfinity.co/v2/sequencer/sequences/SEQ_ID/steps \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{ "user_id": "USER_ID", "step_type": "call", "interval_seconds": 0 }'

# Day 2: an email that sends itself
curl -X POST https://client-api.salesfinity.co/v2/sequencer/sequences/SEQ_ID/steps \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{
    "user_id": "USER_ID", "step_type": "auto_email", "interval_seconds": 172800,
    "templates": [{ "template": "TEMPLATE_ID" }]
  }'
```

`auto_email` sends on its own. `call`, `task` and `manual_email` create a task and wait for the rep.

<Warning>
  Reordering or deleting a step is refused with **409** while anyone is mid-cadence — renumbering
  would desync the step each prospect is sitting on. Disable the sequence, edit it, then enable it
  again.
</Warning>

## 3. Preview before you enrol

Preview is free and tells you who would actually go in.

```bash theme={null}
curl -X POST https://client-api.salesfinity.co/v2/sequencer/enrollment-runs/preview \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{
    "user_id": "USER_ID", "sequence": "SEQ_ID",
    "source": { "kind": "contacts", "contacts": ["CONTACT_ID_1", "CONTACT_ID_2"] }
  }'
```

You get a `preview_id`, counts, and a breakdown of everyone excluded and why. Anything Salesfinity
can answer from data it already holds is answered exactly; anything that would need a live CRM read
is reported under `unmeasured` rather than guessed.

Pass the `preview_id` back as a `preview` source to enrol exactly the set you reviewed. Previews
expire after 30 minutes.

## 4. Add contacts

Up to 500 at a time, synchronously:

```bash theme={null}
curl -X POST https://client-api.salesfinity.co/v2/sequencer/sequences/SEQ_ID/contacts \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -H 'Idempotency-Key: 8f14e45f-ea3b-4d1c-9c2a-1b0d3f6a2e77' \
  -d '{
    "user_id": "USER_ID", "owner_id": "REP_ID",
    "prospects": [
      { "email": "dana@acme.com", "first_name": "Dana", "account": "Acme" },
      { "contact": "EXISTING_CONTACT_ID" }
    ]
  }'
```

**A 200 does not mean everyone went in.** The response always reports the whole outcome:

```json theme={null}
{
  "enrolled": 1,
  "skipped": [{ "prospect": "email:dana@acme.com", "reason": "suppressed" }],
  "held": [],
  "warnings": []
}
```

Read `skipped` on every call. The reasons are: `suppressed` (on your do-not-contact list),
`already_enrolled` (a contact can hold only one live enrollment per sequence), `policy` (a rule
blocked them), `throttle_capacity` / `throttle_daily`, and `contact_not_found`.

For more than 500 people, start an enrolment run instead and poll it — it handles up to 5000 in the
background.

### Retrying safely

Send an `Idempotency-Key` on writes. A repeat with the same key returns the original response
instead of acting twice; a repeat with the same key but a *different* body is a 409, because that
means something is wrong on your side.

## 5. Turn it on

```bash theme={null}
curl -X POST https://client-api.salesfinity.co/v2/sequencer/sequences/SEQ_ID/enable \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{ "user_id": "USER_ID" }'
```

Anyone already parked in the sequence starts moving, and the response says how many. A sequence
containing an email step is refused unless the team has at least one mailbox that is connected,
unpaused and able to send — check with `GET /v2/sequencer/email-accounts`.

## 6. Work the task queue

```bash theme={null}
# Everything due, across the team
curl 'https://client-api.salesfinity.co/v2/sequencer/tasks?overdue=true&sort=priority&direction=desc' \
  -H 'x-api-key: YOUR_API_KEY'

# Just the call queue for one rep
curl 'https://client-api.salesfinity.co/v2/sequencer/tasks?action=call&user_id=REP_ID' \
  -H 'x-api-key: YOUR_API_KEY'
```

Sorting and filtering are applied across the whole result set, not just the page you asked for.

Resolve a task:

```bash theme={null}
curl -X POST https://client-api.salesfinity.co/v2/sequencer/tasks/TASK_ID/complete \
  -H 'x-api-key: YOUR_API_KEY' -H 'content-type: application/json' \
  -d '{ "user_id": "REP_ID", "call_log": "CALL_LOG_ID" }'
```

Completing or skipping a task advances the cadence — skipping still moves the contact on. Snoozing
moves the due date, and moves the auto-skip deadline with it.

<Note>
  If the call was dialled inside Salesfinity you do not need to call this at all: dispositioning the
  call completes the task and applies the outcome for you.
</Note>

<Warning>
  There is no bulk complete, deliberately. Completing a `manual_email` task queues a **real email**,
  so a bulk complete would send unreviewed drafts to strangers. Skip and snooze are available in
  bulk; completing is one at a time.
</Warning>

## 7. Removing someone

Un-enrolling is `POST /v2/sequencer/enrollments/{id}/finish`. **There is no DELETE** — an enrollment
is a record of what happened, so it is finished rather than destroyed and its timeline survives.
Once finished, the same contact can be enrolled again later.

To stop contacting an entire company at once — you booked a meeting and want everyone else left
alone — use `POST /v2/sequencer/enrollments/bulk/account`. Send `"dry_run": true` first; it can
touch a lot of people.

## 8. Find out why someone was not added

```bash theme={null}
curl 'https://client-api.salesfinity.co/v2/sequencer/sequences/SEQ_ID/enrollment-attempts' \
  -H 'x-api-key: YOUR_API_KEY'
```

This is the counterpart to the enrollments list, and the first place to look when a batch adds fewer
people than you sent.

## Staying in sync

Rather than polling, subscribe to webhooks in the dashboard under **Settings → Connections & API**.
The sequencer events are `SEQUENCE_CONTACT_ENROLLED`, `SEQUENCE_CONTACT_PAUSED`,
`SEQUENCE_CONTACT_FINISHED`, `SEQUENCE_APPROVAL_REQUESTED`, `SEQUENCE_TASK_CREATED`,
`SEQUENCE_TASK_COMPLETED`, `SEQUENCE_TASK_SKIPPED`, `SEQUENCE_EMAIL_SENT`,
`SEQUENCE_EMAIL_REPLIED`, `SEQUENCE_EMAIL_BOUNCED` and `SEQUENCE_EMAIL_OPTED_OUT`.

Each delivery carries an `x-salesfinity-signature` header: `sha256=` followed by the HMAC-SHA256 of
the raw request body, keyed with the signing secret shown once when you create the webhook. Verify
it with a constant-time comparison before trusting the payload. Compute the digest over the bytes as
received — re-serializing the JSON first will change them and the comparison will fail.

A webhook created before signing existed has no secret, so its deliveries arrive without the header.
There is currently no way to issue a secret for one; recreate the webhook to get a signed feed.

**Make your receiver idempotent.** A delivery that fails with a 5xx, a 429, or a transport fault is
retried up to three times with backoff, so the same event can arrive more than once — for example if
your endpoint times out after it has already processed the payload. Deliveries are independent per
subscription and are not ordered, so do not infer sequence from arrival order. A 4xx is not retried:
the event is dropped and recorded on the webhook's delivery log, which you can read in the dashboard.
Each attempt is bounded at 10 seconds, so return quickly and do your work afterwards.

## Rate limits and errors

`/v2/sequencer/*` allows 120 requests per minute per API key. Over that you get a **429** with a
`Retry-After` header. Retry 429 and 5xx with exponential backoff and jitter; 400, 403 and 404 will
not succeed on an identical retry.

Every error uses the same envelope as the rest of the API — see [Errors](/api-reference/errors).
