---
title: "Create a sequence"
method: POST
path: "/v1/projects/{project}/sequences"
tags: ["Recovery Sequences"]
---

# Create a sequence

`POST /v1/projects/{project}/sequences`

## Request

- Base URL: `https://api.subscriby.net`
- URL: `https://api.subscriby.net/v1/projects/{project}/sequences`
- Auth: HTTP bearer

```bash
curl -X POST https://api.subscriby.net/v1/projects/7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13/sequences \
  -H "Authorization: Bearer sbt_..." \
  -H "Idempotency-Key: 6b1f0d2e-9c3a-4e8b-b2d1-7a9c5e3f1d04" \
  -H "Content-Type: application/json" \
  -d '{"name": "Win-back after churn", "steps": [{"delay_days": 3, "body": "Hi {name}, your place in {project} is still here: {link}"}, {"delay_days": 7, "body": "{coupon} takes {discount} off: {link}", "coupon_id": "3b7c9d1e-5f24-4a86-9c0b-2d4e6f8a1b3c"}]}'
```

Builds the sequence with its steps in one write and answers it with `201`; `trigger` defaults to `churned` and `active` to `true`. Up to 8 steps, the first 0 to 180 days after the trigger (`0` goes out on the next hourly run) and each later one 1 to 180 days after the one before, with a message of 1 to 1,000 characters. A sequence built without steps exists and enrols nobody until it has one.

> The body is sent as a chat message, so a step with a coupon reads best when it uses `{coupon}` and `{discount}`, and every step should end on `{link}`, which is the way back.

## Path parameters

- `project` string, uuid, required

## Headers

- `Idempotency-Key` string, uuid, required

## Request body

- StoreSequenceRequest: A sequence to build: its name, what starts it, whether it runs, and the steps in the order they go out. Needs the Growth plan or the Revenue Recovery Addon.
  - `name` string, required: A creator-facing label, 1 to 120 characters; such as `Win-back after churn`.
  - `trigger` 'churned' | 'lead' | 'stalled_checkout' | 'trial_ending': What enrols a member: `churned` (their last live membership ended; the default), `lead` (they arrived without buying), `stalled_checkout` (a checkout they started stayed open past the project's window) or `trial_ending` (a trial without a card is about to end).
  - `active` boolean: Whether the sequence enrols and sends from the start. Defaults to `true`.
  - `steps` object[]: The steps in the order they go out, at most 8. Omit for a sequence with no steps yet, which enrols nobody.
    - `delay_days` integer, required: Days after the trigger for the first step, 0 to 180 (`0` goes out on the next hourly run); days after the step before for the rest, 1 to 180.
    - `body` string, required: The message in the creator's words, 1 to 1,000 characters of text, with `{name}`, `{project}`, `{coupon}`, `{discount}` and `{link}` filled per member at send time. Plain text or HTML: bold, italic, underline, strikethrough, code, links and paragraphs are kept, everything else is stripped to its words.
    - `coupon_id` string, nullable: One of this project's coupons to carry with the step, or `null`.

## Response `201`

The new sequence, `201`.

- object
  - `data` SequenceResource, required: A sequence: the ordered messages a member receives on the days the creator chose after one of four moments (they left, they arrived as a lead, their checkout stalled, their trial is about to end), each with the way to the plan and, optionally, a coupon; with how many members it enrolled, how many are still receiving it and how many paid while it ran.
    - `id` string, required: The sequence's id.
    - `project_id` string, required: The project the sequence belongs to.
    - `name` string, required: A creator-facing label.
    - `trigger` string, required: What enrols a member: `churned` (their last live membership ended), `lead` (they arrived without buying), `stalled_checkout` (a checkout stayed open past the project's window) or `trial_ending` (a trial without a card is about to end).
    - `active` boolean, required: Whether the sequence enrols and sends.
    - `steps` SequenceStepResource[]: The steps in order, present when the relation is loaded.
      - `id` string, required: The step's id. A step rewritten in place keeps it.
      - `position` integer, required: The step's place, contiguous from 1.
      - `delay_days` integer, required: Days after the trigger for the first step, 0 to 180 (`0` goes out on the next hourly run); days after the step before for the rest, 1 to 180.
      - `body` string, required: The message with its placeholders unfilled: `{name}`, `{project}`, `{coupon}`, `{discount}` and `{link}`.
      - `coupon` object, nullable: The coupon the step carries, present when the relation is loaded; `null` without one.
        - `id` string, required: The coupon's id.
        - `code` string, required: The coupon's code.
    - `counts` object, required: How the sequence has done, all time.
      - `enrolled` string, required: Every member ever enrolled.
      - `open` string, required: Members still receiving steps.
      - `completed` string, required: Members who received every step.
      - `won_back` string, required: Members for whom a payment settled while the sequence ran.
    - `created_at` string, nullable, required: When the sequence was built, ISO 8601.
    - `updated_at` string, nullable, required: When it last changed, ISO 8601.
    - `deleted_at` string, nullable, required: When the creator deleted it, ISO 8601; `null` while in use.

## Other responses

- `400`: `IDEMPOTENCY_KEY_MISSING`: every write needs an `Idempotency-Key` header. Send a fresh UUID per distinct operation.
- `401`: `AUTHENTICATION_REQUIRED`: the request carries no bearer token, or one that is revoked, malformed, or minted for another environment (an `sbt_test_` token on production).
- `403`: `TOKEN_MISSING_ABILITY`: the token is valid but does not carry the ability this endpoint requires; `error.context.required_ability` names the one to grant. An endpoint that also checks who owns a row or which tier the account is on answers `FORBIDDEN`, `TEAM_TIER_REQUIRED` or `CONNECTOR_TIER_REQUIRED` with the same status, and says so in its own description.
- `404`: `RESOURCE_NOT_FOUND`: an id in the path names nothing the token can see. `TENANT_MISMATCH`: the project sits outside the token's `scope:project:` allow-list, or the token carries no team scope. Both answer 404 rather than 403 so that existence outside the token's scope cannot be inferred.
- `409`: `IDEMPOTENCY_KEY_REUSED`: the key was already used in the last 24 hours with a different request body.
- `422`: `VALIDATION_FAILED`: the payload broke a rule, and `error.fields` maps each offending key to its messages. A refusal from the domain, such as a plan that cannot go on sale or a member who cannot be removed, uses the same code with `error.message` saying why and no `fields`.
- `425`: `IDEMPOTENCY_REPLAY_IN_PROGRESS`: the first request with this key is still running; retry in a few seconds and the original response is replayed.
- `429`: `RATE_LIMITED`: the token has spent its 300 requests a minute or 10,000 an hour; `Retry-After` says when the next one is accepted.

---

[API](https://skmtc.dev/memberpass/apis/subscriby-rest-api.md) · [All operations](https://skmtc.dev/memberpass/apis/subscriby-rest-api/llms.txt) · [OpenAPI document](https://skmtc.dev/memberpass/apis/subscriby-rest-api/revisions/59c4df91d764?raw)
