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

# Create Sequence

> Create an automation sequence with AI-generated or explicit email/action steps

# Create Sequence

Create an automation sequence that sends emails based on triggers. You can:

* Provide a `goal` to have AI generate the email content
* Provide `steps` with explicit email content and actions such as discount creation or Update Subscriber
* Omit both `goal` and `steps` to create the same blank trigger-to-completion draft the dashboard starts with

New sequences are created as drafts, so the response reports
`"effectiveStatus": "draft"` and nobody is enrolled until you call
[Enable Sequence](/api-reference/sequences/enable). See
[List Sequences](/api-reference/sequences/list) for the full run-state mapping.

## Request

<ParamField body="name" type="string" required>
  Name for the sequence
</ParamField>

<ParamField body="description" type="string">
  Dashboard description for the sequence.
</ParamField>

<ParamField body="labels" type="string[]">
  Dashboard label names. Missing labels are created automatically.
</ParamField>

<ParamField body="userCancellable" type="boolean">
  Whether recipients can cancel this sequence from email preferences.
</ParamField>

<ParamField body="bccEmails" type="string[]">
  Up to 10 addresses blind-copied on every sequence email.
</ParamField>

<ParamField body="trigger" type="string">
  Trigger type: `contact_added`, `tag_added`, `segment_entered`,
  `event_received`, `inbound_webhook`, `inactivity`, or `frequency`. Defaults to
  `contact_added` when omitted.
</ParamField>

<ParamField body="fromEmail" type="string">
  From address for every email in this sequence. Its domain must be configured
  and verified. Mutually exclusive with `senderProfileId`.
</ParamField>

<ParamField body="fromName" type="string">
  Display name for a newly created sender profile. Requires `fromEmail`; omit it
  when you send `senderProfileId`, which already carries its own display name.
</ParamField>

<ParamField body="senderProfileId" type="string">
  Existing sender profile ID. It already supplies both the From address and the
  display name, so send it on its own and omit `fromEmail` and `fromName`.
</ParamField>

<ParamField body="replyTo" type="string">
  Reply-To address for every email in this sequence. Mutually exclusive with
  `replyProfileId`.
</ParamField>

<ParamField body="replyToName" type="string">
  Display name for a newly created reply profile. Requires `replyTo`; omit it
  when you send `replyProfileId`, which already carries its own display name.
</ParamField>

<ParamField body="replyProfileId" type="string">
  Existing reply profile ID. It already supplies both the Reply-To address and
  the display name, so send it on its own and omit `replyTo` and `replyToName`.
</ParamField>

<ParamField body="listId" type="string">
  List ID (for `contact_added` trigger)
</ParamField>

<ParamField body="tagName" type="string">
  Tag name (for `tag_added` trigger)
</ParamField>

<ParamField body="segmentId" type="string">
  Segment ID (for `segment_entered` trigger)
</ParamField>

<ParamField body="stopOnSegmentExit" type="boolean">
  For `segment_entered`, cancel an enrollment when the subscriber leaves the
  segment.
</ParamField>

<ParamField body="eventName" type="string">
  Event name (for `event_received`, `inactivity`, `frequency` triggers)
</ParamField>

<ParamField body="integrationSlug" type="string">
  Integration slug for an `inbound_webhook` trigger.
</ParamField>

<ParamField body="integrationEventKey" type="string">
  Integration event key for an `inbound_webhook` trigger.
</ParamField>

<ParamField body="customIntegration" type="object">
  Custom integration metadata for an `inbound_webhook` trigger.
</ParamField>

<ParamField body="propertyFilters" type="array">
  Event property filters (for `event_received` and `inbound_webhook` triggers,
  maximum 10).
  The sequence only starts when the triggering event's properties match ALL
  filters. Use a dot-path into the event properties; use `[]` to match items
  inside arrays, for example `lineItems[].providerProductId`.

  <Expandable title="Filter properties">
    <ParamField body="path" type="string" required>
      Dot-path into the event properties, such as `plan`,
      `lineItems[].providerProductId`, or `productIds`.
    </ParamField>

    <ParamField body="operator" type="string" required>
      One of `exists`, `not_exists`, `equals`, `not_equals`, `one_of`,
      `contains`, `greater_than`, or `less_than`. `one_of` matches when the
      property equals any entry of the value array - for example, scope a
      trigger to several products with
      `{"path": "lineItems[].providerProductId", "operator": "one_of", "value": ["prod_1", "prod_2"]}`.
    </ParamField>

    <ParamField body="value" type="string | number | boolean | array">
      Value to compare against. Required for every operator except `exists`
      and `not_exists`. For `one_of`, pass a non-empty array of strings or
      numbers with at most 50 values; all other operators take a single
      value.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="inactiveDays" type="number">
  Days of inactivity (for `inactivity` trigger)
</ParamField>

<ParamField body="inactivityBaseline" type="string">
  For `inactivity` triggers, when to start counting for subscribers who have
  never performed the event: `sequence_created_at` or `subscriber_created_at`.
  Defaults to `sequence_created_at`.
</ParamField>

<ParamField body="minCount" type="number">
  Minimum event count (for `frequency` trigger)
</ParamField>

<ParamField body="timeWindowDays" type="number">
  Time window in days (for `frequency` trigger)
</ParamField>

<ParamField body="enrollmentMode" type="string">
  Re-entry mode: `unlimited`, `one_time`, or `matching_field`. Defaults to
  `matching_field` for Shopify replenishment and back-in-stock events, otherwise
  `unlimited`. `matching_field` is only valid for `event_received` and
  `inbound_webhook` sequence triggers.
</ParamField>

<ParamField body="enrollmentFieldPath" type="string">
  Scalar dot-path event property used by `matching_field`, such as `order.id` or
  `product.providerVariantId`. Array traversal with `[]` is not supported; use
  `propertyFilters` when the trigger must match values inside an array. Leave
  empty for the built-in Shopify product/variant default.
</ParamField>

<ParamField body="sendingWindow" type="object">
  Optional local-time sending window for every email step in the sequence. Email
  steps that become due outside the window wait until the next allowed local
  time.

  <Expandable title="Sending window properties">
    <ParamField body="enabled" type="boolean">
      Set to `false` to disable the window. Omit `sendingWindow` when you do not
      want a sending restriction.
    </ParamField>

    <ParamField body="timezone" type="string" required>
      IANA timezone for the window, such as `Europe/Kiev` or
      `America/New_York`.
    </ParamField>

    <ParamField body="startTime" type="string" required>
      Earliest local send time in `HH:mm` format, for example `08:00`.
    </ParamField>

    <ParamField body="endTime" type="string" required>
      Latest local send cutoff in `HH:mm` format, for example `20:00`. Must be
      later than `startTime`.
    </ParamField>

    <ParamField body="days" type="array">
      Allowed local days. Use full day names such as `monday`; omit to allow
      every day.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="stopCondition" type="object">
  Optional auto-stop condition, re-evaluated before every step including the
  first one. Use `has_tag` to end the sequence when a subscriber gets a tag,
  `event_received` when an event is tracked, `added_to_list` when a subscriber
  joins a list, `entered_segment` when a subscriber enters a segment,
  `field_changed` when a subscriber attribute changes, or `none` to clear
  auto-stop behavior. `does_not_have_tag` and `removed_from_list` stop the run
  whenever the subscriber lacks that tag or list membership, so they act as a
  required-tag or required-list allowlist and cancel everyone else before any
  step sends. Guarded-out contacts still enroll and are then cancelled at the
  trigger node, so they appear as cancellations there rather than in the active
  or waiting enrollment counts. Clearing the guard does not retry them: they
  only receive the sequence if the trigger fires for them again, and on the
  `one_time` enrollment mode not even then. `event_received` can include
  `matchConfig.mode: "event_property"` with field mapping rules; `field_changed`
  can include `matchConfig.mode: "field_value"` with an operator and comparison
  value.
</ParamField>

### Option A: AI Generation

<ParamField body="goal" type="string">
  Description of what the sequence should accomplish. AI will generate email
  content based on this.
</ParamField>

<ParamField body="emailCount" type="number">
  Number of emails to generate (default: `5`, maximum: `10`)
</ParamField>

<ParamField body="durationDays" type="number">
  Total duration in days used to space AI-generated emails. Omit this to use the
  default sequence delay schedule.
</ParamField>

<ParamField body="emailStyle" type="string">
  Style for the AI-generated emails: `visual` (designed, with heroes, imagery,
  and rich sections) or `plain` (personal, text-first notes with a single
  button). Defaults to the company's saved preference when omitted.
</ParamField>

### Option B: Explicit Content

<ParamField body="steps" type="array">
  Array of explicit steps. Email steps must have either `blocks` or `html`; SMS steps use `type: "sms"` with `text`; discount steps use `type: "create_discount"`; Update Subscriber steps use `type: "update_subscriber"`, `nodeType: "action_update_attributes"`, and `config`.

  <Expandable title="Step properties">
    <ParamField body="type" type="string">
      Step type. Omit or use `email` for email content. Use `sms` for a native SMS step, `create_discount` for a dynamic discount, or `update_subscriber` to copy event data onto the subscriber.
    </ParamField>

    <ParamField body="nodeType" type="string">
      Use `action_update_attributes` for Update Subscriber steps.
    </ParamField>

    <ParamField body="config" type="object">
      Update Subscriber step config. `firstName`, `lastName`, and
      `customAttributeUpdates[].value` can use standalone merge tags such as
      `{{event.plan}}`, `{{event.amount}}`, and `{{event.active}}`. Set
      `valueType` to `text`, `number`, or `boolean`; number and boolean values
      are coerced after resolution.
    </ParamField>

    <ParamField body="subject" type="string" required>
      Email subject line. Required for email steps.
    </ParamField>

    <ParamField body="text" type="string">
      SMS steps only: plain-text message body. Merge tags like `{{FIRST_NAME}}` work. Do not include opt-out text or a brand prefix - Sequenzy adds both automatically at send time. Generate drafts with the [Generate SMS endpoint](/api-reference/generate/sms).
    </ParamField>

    <ParamField body="imageUrls" type="array">
      SMS steps only: up to 2 publicly reachable image URLs sent as MMS media.
    </ParamField>

    <ParamField body="ineligibleAction" type="string">
      SMS steps only: `skip` (default) continues the sequence when the contact can't receive SMS (no phone, not opted in, unsupported country); `exit` removes them from the sequence.
    </ParamField>

    <ParamField body="label" type="string">
      SMS steps only: display label for the step in the builder.
    </ParamField>

    <ParamField body="previewText" type="string">
      Email preview text
    </ParamField>

    <ParamField body="isTransactional" type="boolean">
      Use transactional chrome for this sequence email.
    </ParamField>

    <ParamField body="ccEmails" type="string[]">
      Per-step visible copy recipients.
    </ParamField>

    <ParamField body="bccEmails" type="string[]">
      Per-step blind-copy recipients.
    </ParamField>

    <ParamField body="attachments" type="array">
      URL-backed file attachments for this email step, as `{ filename, path }`
      objects. `path` can be a public HTTPS URL or, for an event-triggered
      sequence, an event value such as `{{event.file_url}}`. `filename` can
      also contain event merge tags, for example
      `file-{{event.order_id}}.pdf`. Values are resolved from the event that
      enrolled the subscriber, then the final URL is validated and fetched at
      send time (max 10 attachments, 7MB total per email). Base64 content is
      not supported here.
    </ParamField>

    <ParamField body="blocks" type="array">
      Email content blocks in Sequenzy format.
      Provide `blocks` OR `html`, not both. Put block styling under `styles`;
      top-level style keys like `backgroundColor`, `backgroundOpacity`,
      `borderColor`, `borderWidth`, and `borderRadius` are normalized into
      `styles`.
    </ParamField>

    <ParamField body="html" type="string">
      Raw HTML content (plain HTML, React Email output, MJML output, etc.).
      Stored as one raw HTML block; Sequenzy does not convert it to native
      blocks.
      Provide `html` OR `blocks`, not both.
    </ParamField>

    <ParamField body="delay" type="object">
      Fixed delay before this email. To wait until a date from the enrollment
      event/subscriber data, set `mode` to `until_date` and provide
      `untilDateField`, or use the `waitUntil` shorthand.

      <Expandable title="Delay properties">
        <ParamField body="mode" type="string">
          `duration` for fixed waits or `until_date` for dynamic date-field
          waits. Defaults to `duration`.
        </ParamField>

        <ParamField body="days" type="number">Days to wait</ParamField>
        <ParamField body="hours" type="number">Hours to wait</ParamField>
        <ParamField body="minutes" type="number">Minutes to wait</ParamField>

        <ParamField body="untilDateField" type="string">
          Date field path to wait until when `mode` is `until_date`, for
          example `renews_at`.
        </ParamField>

        <ParamField body="untilOffsetDirection" type="string">
          `before` or `after`. Defaults to `after`.
        </ParamField>

        <ParamField body="untilMissingAction" type="string">
          `continue` or `exit` when the date field is missing or invalid.
          Defaults to `continue`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="waitUntil" type="object">
      Shorthand for a dynamic delay that waits until a date field from the
      enrollment event/subscriber data.

      <Expandable title="Wait-until properties">
        <ParamField body="field" type="string" required>
          Date field path, for example `renews_at` or
          `event.subscription.renews_at`.
        </ParamField>

        <ParamField body="offset" type="object">
          Optional offset object with `days`, `hours`, and `minutes`.
        </ParamField>

        <ParamField body="direction" type="string">
          `before` or `after`. Defaults to `after`.
        </ParamField>

        <ParamField body="missingAction" type="string">
          `continue` or `exit` when the date field is missing or invalid.
          Defaults to `continue`.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField body="delayMs" type="number">
      Delay before this step in milliseconds. Prefer `delay` for hand-written
      requests; use `delayMs` when importing provider waits.
    </ParamField>

    <ParamField body="name" type="string">
      Optional name for the email template.
    </ParamField>

    <ParamField body="discount" type="object">
      Discount configuration for `create_discount` steps. For new integrations, put discount options here; legacy top-level discount fields are still accepted.

      <Expandable title="Discount properties">
        <ParamField body="label" type="string">
          Builder label for discount steps.
        </ParamField>

        <ParamField body="provider" type="string">
          Discount provider. Use `stripe` to dynamically create a Stripe coupon plus promotion code, or `shopify` to dynamically create a Shopify Admin discount code.
        </ParamField>

        <ParamField body="discountType" type="string">
          Discount type: `percent` or `amount`.
        </ParamField>

        <ParamField body="percentOff" type="number">
          Percent discount from `1` to `100`. Required when `discountType` is `percent`.
        </ParamField>

        <ParamField body="amountOff" type="number">
          Fixed discount in the smallest currency unit, for example `500` for `$5`. Required when `discountType` is `amount`.
        </ParamField>

        <ParamField body="currency" type="string">
          Currency for fixed-amount discounts. Defaults to `usd`.
        </ParamField>

        <ParamField body="duration" type="string">
          Coupon duration: `once`, `forever`, or `repeating`. Defaults to `once`.
        </ParamField>

        <ParamField body="durationInMonths" type="number">
          Required for `repeating` discounts.
        </ParamField>

        <ParamField body="appliesToAllPlans" type="boolean">
          Whether the discount applies to all plans. Defaults to `true`.
        </ParamField>

        <ParamField body="planIds" type="array">
          Provider product IDs when `appliesToAllPlans` is `false`. Stripe uses IDs like `prod_abc123`; Shopify accepts numeric product IDs or `gid://shopify/Product/...` IDs.
        </ParamField>

        <ParamField body="codePrefix" type="string">
          Optional prefix for generated dynamic codes. The final code also includes a subscriber/token suffix.
        </ParamField>

        <ParamField body="maxRedemptions" type="number">
          Maximum redemptions for each generated code. Use `1` for subscriber-specific codes.
        </ParamField>

        <ParamField body="lockToSubscriber" type="boolean">
          Stripe-only. When `true`, each generated promotion code is restricted to the matched subscriber's Stripe customer.
        </ParamField>

        <ParamField body="expiresAt" type="string">
          Optional future expiration date or ISO timestamp. Mutually exclusive with `expiresInHours`.
        </ParamField>

        <ParamField body="expiresInHours" type="number">
          Optional relative expiration in hours, resolved to an absolute timestamp when each subscriber's code is created. Use this for flows like "valid for 48 hours" that should restart per enrollment. Takes precedence over `expiresAt` when both are provided.
        </ParamField>

        <ParamField body="name" type="string">
          Optional display name for each dynamically generated provider discount.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

### Option C: Blank Draft

Send only `name` (and optionally metadata or a trigger). The API creates a
disabled draft containing the trigger and completion nodes, ready for later
`insertSteps`, branch insertion, or graph edits.

## Discount Merge Tags

When a `create_discount` step runs, Sequenzy creates a fresh provider discount/code for that subscriber's automation run. Later emails in the same sequence can use discount merge tags from the most recent created discount:

* `{{discount.code}}`
* `{{discount.percentOff}}`
* `{{discount.amountOff}}`
* `{{discount.currency}}`
* `{{discount.expiresAt}}`

Discount sequences require a connected integration for the selected provider, such as Stripe or Shopify, before activation.

## Enrollment Modes

Use `matching_field` for event-triggered sequences where each run belongs to a specific object in the event payload. It lets a subscriber have separate active runs for different products, variants, orders, subscriptions, or other event-scoped objects, while blocking duplicate active runs for the same resolved field. Set `enrollmentFieldPath` for custom events, or send `sequenceEnrollmentKey`, `enrollmentKey`, or common product/variant fields in the event properties.

```json theme={null}
{
  "trigger": "event_received",
  "eventName": "ecommerce.order_placed",
  "enrollmentMode": "matching_field",
  "enrollmentFieldPath": "order.id"
}
```

## Sending Windows

Use `sendingWindow` when a triggered sequence should start immediately but emails
should only go out during allowed local hours. The window is checked before every
email action, including later steps after delays or branches.

```json theme={null}
{
  "sendingWindow": {
    "enabled": true,
    "timezone": "Europe/Kiev",
    "startTime": "08:00",
    "endTime": "20:00",
    "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
  }
}
```

## Event-Personalized Sequence Emails

If you create a sequence with `trigger: "event_received"`, the event payload that enrolls the subscriber is available inside sequence emails through `event.` merge tags.

For example, if your app sends:

```json theme={null}
{
  "email": "user@example.com",
  "event": "weather.wind_alert",
  "properties": {
    "city": "Tel Aviv",
    "alert": {
      "maxSpeed": 75
    },
    "duration": {
      "hours": 6
    }
  }
}
```

You can use values like:

* `{{event.city}}`
* `{{event.alert.maxSpeed}}`
* `{{event.duration.hours|a few}}`

Sequenzy stores the event snapshot on the sequence run, so delayed follow-up emails in the same sequence keep using the original values.

## Examples

### AI-Generated Content

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Onboarding Sequence",
    "trigger": "contact_added",
    "stopCondition": {
      "type": "has_tag",
      "value": "customer"
    },
    "sendingWindow": {
      "enabled": true,
      "timezone": "Europe/Kiev",
      "startTime": "08:00",
      "endTime": "20:00",
      "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
    },
    "goal": "Welcome new users and help them get started with our product",
    "emailCount": 5,
    "durationDays": 14
  }'
```

### Explicit Content with Blocks

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome Sequence",
    "trigger": "contact_added",
    "steps": [
      {
        "subject": "Welcome to our product!",
        "blocks": [
          {
            "id": "heading-1",
            "type": "heading",
            "content": "Welcome!",
            "level": 1
          },
          {
            "id": "text-1",
            "type": "text",
            "content": "<p>Thanks for signing up. We are excited to have you.</p>",
            "variant": "paragraph"
          }
        ]
      },
      {
        "subject": "Getting started guide",
        "delay": { "days": 1 },
        "blocks": [
          {
            "id": "heading-2",
            "type": "heading",
            "content": "Getting Started",
            "level": 1
          },
          {
            "id": "text-2",
            "type": "text",
            "content": "<p>Here is everything you need to get started...</p>",
            "variant": "paragraph"
          }
        ]
      }
    ]
  }'
```

### Explicit Content with HTML (Auto-converted)

Use this when migrating from other platforms or when you have existing HTML templates (React Email, MJML, etc.).

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Migrated Sequence",
    "trigger": "contact_added",
    "steps": [
      {
        "subject": "Welcome!",
        "html": "<h1>Welcome to our platform!</h1><p>We are thrilled to have you on board.</p><a href=\"https://example.com/start\">Get Started</a>"
      },
      {
        "subject": "Quick tips to get started",
        "delay": { "days": 2 },
        "html": "<h2>Getting Started Guide</h2><p>Here are some tips:</p><ul><li>Complete your profile</li><li>Explore the dashboard</li><li>Connect your integrations</li></ul>"
      }
    ]
}'
```

### Explicit Discount Action

Use this when you want Sequenzy to generate a dynamic provider discount code before sending a follow-up email. Connect the selected provider in Settings before enabling the sequence.

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Win-back Discount",
    "trigger": "tag_added",
    "tagName": "cancelled",
    "steps": [
      {
        "type": "create_discount",
        "discount": {
          "label": "Create win-back discount",
          "provider": "stripe",
          "discountType": "percent",
          "percentOff": 20,
          "duration": "once",
          "appliesToAllPlans": true,
          "maxRedemptions": 1,
          "lockToSubscriber": true,
          "codePrefix": "WINBACK"
        }
      },
      {
        "subject": "Come back with {{discount.code}}",
        "delay": { "days": 1 },
        "html": "<p>Use {{discount.code}} for {{discount.percentOff}}% off your next subscription.</p>"
      }
    ]
  }'
```

### Inactivity Sequence

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Login Win-back",
    "trigger": "inactivity",
    "eventName": "app.login",
    "inactiveDays": 14,
    "inactivityBaseline": "sequence_created_at",
    "goal": "Bring inactive users back into the app"
  }'
```

### Product-Scoped Purchase Sequence (Property Filters)

Use `propertyFilters` to start a purchase sequence only for one specific product, for example to deliver a digital product file. Use `lineItems[].providerProductId` for `ecommerce.order_placed` events, or `productIds` for Stripe `saas.purchase` events.

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ebook Delivery",
    "trigger": "event_received",
    "eventName": "saas.purchase",
    "propertyFilters": [
      {
        "path": "productIds",
        "operator": "equals",
        "value": "prod_ABC123"
      }
    ],
    "steps": [
      {
        "subject": "Your download is ready",
        "html": "<p><a href=\"{{event.download.url}}\">Download {{event.download.name}}</a></p>"
      }
    ]
  }'
```

### Event-Triggered Sequence With `{{event.*}}`

```bash theme={null}
curl -X POST "https://api.sequenzy.com/api/v1/sequences" \
  -H "Authorization: Bearer seq_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Wind Alert Follow-up",
    "trigger": "event_received",
    "eventName": "weather.wind_alert",
    "steps": [
      {
        "subject": "Wind alert for {{event.city}}",
        "html": "<p>Winds may reach {{event.alert.maxSpeed}} km/h for {{event.duration.hours|a few}} hours.</p>"
      },
      {
        "subject": "Stay safe in {{event.city}}",
        "delay": { "hours": 12 },
        "html": "<p>The original alert is still active for {{event.city}}. Check local guidance if conditions change.</p>"
      }
    ]
  }'
```

## Responses

<ResponseExample>
  ```json 200 AI Generation theme={null}
  {
    "success": true,
    "message": "Sequence created. AI is generating email content based on your brand. This typically takes 30-60 seconds.",
    "sequence": {
      "id": "seq_abc123",
      "name": "Onboarding Sequence",
      "status": "draft",
      "enrollmentPaused": false,
      "effectiveStatus": "draft",
      "acceptsNewEnrollments": false,
      "processesExistingEnrollments": false,
      "effectiveStatusSummary": "Draft. Nobody is enrolled and no emails are sent.",
      "emailCount": 5,
      "sendingWindow": {
        "enabled": true,
        "timezone": "Europe/Kiev",
        "startTime": "08:00",
        "endTime": "20:00",
        "days": ["monday", "tuesday", "wednesday", "thursday", "friday"]
      }
    }
  }
  ```

  ```json 200 Explicit Content theme={null}
  {
    "success": true,
    "message": "Sequence created with 2 emails and 1 discount action spanning 1 day. Use POST /api/v1/sequences/{id}/enable to activate.",
    "sequence": {
      "id": "seq_abc123",
      "name": "Welcome Sequence",
      "status": "draft",
      "enrollmentPaused": false,
      "effectiveStatus": "draft",
      "acceptsNewEnrollments": false,
      "processesExistingEnrollments": false,
      "effectiveStatusSummary": "Draft. Nobody is enrolled and no emails are sent.",
      "emailCount": 2,
      "discountCount": 1,
      "nodeCount": 6,
      "sendingWindow": null
    }
  }
  ```

  ```json 200 Custom Event With Matching-Field Enrollment theme={null}
  {
    "success": true,
    "message": "Sequence created with 1 email. Use POST /api/v1/sequences/{id}/enable to activate. Add the custom event feed using eventTrackingCode and eventTracking before enabling.",
    "sequence": {
      "id": "seq_abc123",
      "name": "Trial Started",
      "status": "draft",
      "enrollmentPaused": false,
      "effectiveStatus": "draft",
      "acceptsNewEnrollments": false,
      "processesExistingEnrollments": false,
      "effectiveStatusSummary": "Draft. Nobody is enrolled and no emails are sent.",
      "emailCount": 1,
      "nodeCount": 3
    },
    "eventTrackingCode": "await fetch(\"https://api.sequenzy.com/api/v1/subscribers/events\", { ... properties: { trial_id: \"<trial_id>\" } ... });",
    "eventTracking": {
      "endpoint": "https://api.sequenzy.com/api/v1/subscribers/events",
      "method": "POST",
      "docsUrl": "https://docs.sequenzy.com/api-reference/subscribers/events/trigger",
      "integrationGuide": {
        "tool": "get_integration_guide",
        "arguments": { "use_case": "event_tracking" }
      },
      "payloadContract": {
        "required": ["event", "properties"],
        "identity": "Provide email or externalId. Email is required when the subscriber does not already exist.",
        "event": "Must equal \"trial.started\".",
        "requiredPropertyPaths": ["trial_id"]
      },
      "examplePayload": {
        "email": "user@example.com",
        "event": "trial.started",
        "properties": { "trial_id": "<trial_id>" }
      },
      "examplePayloadMatchesFilters": true
    },
    "requiredEvents": ["trial.started"]
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Either 'goal' (for AI generation) or 'steps' (for explicit content) is required"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Step 1: either 'blocks' or 'html' is required"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Step 1: expiresAt must be in the future"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "Invalid trigger type 'invalid'. Valid types: contact_added, tag_added, segment_entered, event_received, inactivity, frequency"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "propertyFilters is only supported for the event_received trigger"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "propertyFilters[0].value is required for the 'equals' operator"
  }
  ```

  ```json 400 theme={null}
  {
    "error": "emailCount must be no more than 10"
  }
  ```

  ```json 401 theme={null}
  {
    "error": "Invalid API key"
  }
  ```

  ```json 403 theme={null}
  {
    "error": "No companies found. Create a company first using the create_company tool."
  }
  ```
</ResponseExample>

## Notes

* The sequence is created in `draft` status. Use `POST /api/v1/sequences/{id}/enable` to activate it.
* For backwards compatibility, `POST /v1/sequences` is still accepted as an alias.
* When using email `steps`, each email step creates an email template in your account that can be edited later.
* Sequences with `create_discount` steps require a connected integration for the selected provider before activation.
* SMS steps require the SMS add-on (paid plans). Check [Get SMS Settings](/api-reference/sms/settings) first; steps added while SMS is not ready are stored but skip at runtime.
* When using `goal`, AI generates content asynchronously. Poll `GET /api/v1/sequences/{id}` to check enrichment status.
* The first step typically has no delay (sends immediately after trigger).
* If `sendingWindow` is set, each email step sends only during the allowed local
  hours and waits until the next allowed time otherwise.
* For `event_received` sequences, properties sent to `POST /api/v1/subscribers/events` are available as `{{event.*}}` merge tags in sequence emails.
* For custom event triggers, use the returned `eventTrackingCode` and
  `eventTracking` contract to implement the application-owned event feed. The
  contract includes normalized `propertyFilters` and reports whether its
  generated example already satisfies them through
  `examplePayloadMatchesFilters`. If that value is `false`, adapt the example
  using `payloadContract` and `examplePayloadNote` before sending it. With
  `matching_field` enrollment, every required property path must be present in
  `properties` or the sequence cannot identify the matching run.
* When using `html` in steps, the content is preserved as one raw HTML block.
  Use `blocks` when you want native Sequenzy block editing.
