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

# Automations

> Scheduled and state-driven event generation

Automations generate events automatically. Each automation combines a trigger type (when it fires) with a scope (who receives the event). You can use them for monthly bonuses, one-time promotions, birthday rewards, or any event that should happen without an API call from your application.

## Trigger Types and Scopes

| Trigger Type        | Description                                                |
| ------------------- | ---------------------------------------------------------- |
| `cron`              | Fires on a recurring schedule (e.g., first of every month) |
| `one_time`          | Fires once at a specific timestamp                         |
| `immediate`         | Fires as soon as the automation is created                 |
| `participant_state` | Fires when a participant's state matches a condition       |

| Scope          | Description                                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------------------------- |
| `program`      | Sends a single event to the program. Rules evaluate once.                                                     |
| `participants` | Fans out an event to every matching participant. Each event evaluates rules against that participant's state. |

Not all combinations are valid:

|                     | `program` | `participants` |
| ------------------- | --------- | -------------- |
| `cron`              | Yes       | Yes            |
| `one_time`          | Yes       | Yes            |
| `immediate`         | No        | Yes            |
| `participant_state` | No        | Yes            |

When the automation fires, it creates an event with `event_data.type` set to `event_name` and any `payload` fields merged in. That event enters the rules engine like any other event.

## Cron Automations

Cron automations fire on a recurring schedule defined by a standard cron expression.

```bash theme={null}
POST /v1/programs/{programId}/automations
{
  "name": "Weekly Digest",
  "trigger": {
    "type": "cron",
    "cron_expression": "0 9 * * 1",
    "timezone": "America/Chicago"
  },
  "scope": "participants",
  "event_name": "weekly_digest"
}
```

| Field                     | Required | Description                                                                                                                         |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `name`                    | Yes      | Display name (unique per program)                                                                                                   |
| `trigger.type`            | Yes      | `cron`                                                                                                                              |
| `trigger.cron_expression` | Yes      | Standard cron expression. Inline timezone prefixes (`CRON_TZ=`, `TZ=`) are not allowed; use `timezone` instead.                     |
| `trigger.timezone`        | No       | [tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) timezone (e.g., `America/New_York`). Defaults to `UTC`. |
| `scope`                   | Yes      | `program` or `participants`                                                                                                         |
| `event_name`              | Yes      | The event type sent when the automation fires                                                                                       |
| `payload`                 | No       | JSON merged into the event's `event_data`                                                                                           |

The automation tracks `last_run_at`, `last_error`, and `consecutive_failures` for observability. Transient failures do not disable the automation; it stays `active` and fires again on schedule. Only a terminal error, such as a cron expression that can no longer be scheduled, sets status `failed`.

## One-Time Automations

One-time automations fire a single event at a specific timestamp, then transition to `completed`.

```bash theme={null}
POST /v1/programs/{programId}/automations
{
  "name": "New Year Kickoff",
  "trigger": {
    "type": "one_time",
    "trigger_at": "2026-01-01T00:00:00Z"
  },
  "scope": "participants",
  "event_name": "new_year_bonus",
  "payload": {"bonus": 100}
}
```

| Field                | Required | Description                                                 |
| -------------------- | -------- | ----------------------------------------------------------- |
| `trigger.trigger_at` | Yes      | RFC 3339 timestamp. Must be in the future at creation time. |

## Immediate Automations

Immediate automations begin fan-out as soon as they are created. They are always `participants` scope. This is what the `BROADCAST` rule action creates under the hood.

```bash theme={null}
POST /v1/programs/{programId}/automations
{
  "name": "Flash Sale Announcement",
  "trigger": {
    "type": "immediate"
  },
  "scope": "participants",
  "event_name": "flash_sale",
  "payload": {"discount_pct": 20}
}
```

## Participant State Automations

Participant state automations fire events when individual participants meet a condition. Each qualifying participant gets its own subscription with an independent trigger schedule. They are always `participants` scope.

```bash theme={null}
POST /v1/programs/{programId}/automations
{
  "name": "Birthday Reward",
  "trigger": {
    "type": "participant_state",
    "schedule_type": "ATTRIBUTE_DATE",
    "schedule_config": {
      "attribute_key": "birthday",
      "date_format": "MM-DD"
    }
  },
  "scope": "participants",
  "event_name": "birthday",
  "participant_filter": "'birthday' in participant.attributes"
}
```

### Schedule Types

The `schedule_type` determines how each participant's trigger time is computed.

<Note>
  Trigger types use lowercase (`cron`, `one_time`, `immediate`, `participant_state`), while schedule types use uppercase (`ATTRIBUTE_DATE`, `INTERVAL`, `CRON`, `THRESHOLD`).
</Note>

**ATTRIBUTE\_DATE** reads a date from a participant attribute and triggers on that date. `MM-DD` format recurs yearly (birthdays, anniversaries). `YYYY-MM-DD` fires once on the exact date (trial expirations, contract renewals). If the attribute is missing or unparseable, that participant is skipped.

```json theme={null}
{
  "schedule_type": "ATTRIBUTE_DATE",
  "schedule_config": {
    "attribute_key": "birthday",
    "date_format": "MM-DD",
    "offset": "-7d"
  }
}
```

This triggers 7 days *before* each participant's birthday (negative offset). A participant with `birthday: "03-15"` would trigger on March 8th each year.

| Field           | Description                                                                                                                                                                                                                |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `attribute_key` | Participant attribute containing the date value                                                                                                                                                                            |
| `date_format`   | `MM-DD` (recurring annual) or `YYYY-MM-DD` (one-time)                                                                                                                                                                      |
| `offset`        | Optional duration offset from the date, using the Go units `s`, `m`, and `h` plus `d` (days) and `w` (weeks), combinable as in `"1w2d"`. `m` means minutes, not months. Positive triggers after, negative triggers before. |

**INTERVAL** triggers at a fixed duration relative to each participant's subscription. The timer is independent per participant: if participant A subscribes on January 1 and participant B subscribes on January 15, a 30-day interval fires January 31 for A and February 14 for B.

```json theme={null}
{
  "schedule_type": "INTERVAL",
  "schedule_config": {
    "interval": "30d"
  }
}
```

| Field      | Description                                                                                                                                                             |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `interval` | Duration using the Go units `s`, `m`, and `h` plus `d` (days) and `w` (weeks), combinable as in `"1w2d12h"` (e.g., `"30d"` for 30 days). `m` means minutes, not months. |

**CRON** is a calendar-aligned schedule shared across participants. Unlike INTERVAL, all subscribed participants fire at the same wall-clock times regardless of when they were subscribed.

```json theme={null}
{
  "schedule_type": "CRON",
  "schedule_config": {
    "expression": "0 9 * * 1"
  }
}
```

| Field        | Description              |
| ------------ | ------------------------ |
| `expression` | Standard cron expression |

**THRESHOLD** triggers when a participant's counter crosses a value. The optional `delay` gives the participant time to take further action before the event fires.

```json theme={null}
{
  "schedule_type": "THRESHOLD",
  "schedule_config": {
    "counter_key": "lifetime_spend",
    "operator": ">=",
    "threshold": 1000,
    "delay": "24h"
  }
}
```

This fires 24 hours after a participant's `lifetime_spend` counter reaches 1000. If the counter drops below 1000 during the delay, a `guard_condition` can prevent the event from firing.

| Field         | Description                                                                                                                                                                                 |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `counter_key` | Counter to evaluate                                                                                                                                                                         |
| `operator`    | `>=`, `>`, `==`, `<=`, `<`                                                                                                                                                                  |
| `threshold`   | Numeric value to compare against                                                                                                                                                            |
| `delay`       | Duration to wait after the condition is met before firing, using the Go units `s`, `m`, and `h` plus `d` (days) and `w` (weeks), combinable as in `"1d12h"`. `m` means minutes, not months. |

### Filters and Guards

`participant_filter` and `guard_condition` apply to every `participants`-scoped automation (cron, one-time, immediate, and participant state), not only the participant state trigger described in this section. The two optional CEL expressions run at different phases.

**`participant_filter`** runs during evaluation and controls which participants get subscriptions. A birthday automation only makes sense for participants who have a `birthday` attribute:

```json theme={null}
{
  "participant_filter": "'birthday' in participant.attributes"
}
```

Participants where the expression returns `false` are skipped. If a previously qualifying participant no longer matches on re-evaluation, their subscription is cancelled.

**`guard_condition`** runs at trigger time, right before the event fires. Use it when state may have changed between evaluation and trigger:

```json theme={null}
{
  "guard_condition": "participant.counter.lifetime_spend >= 1000.0"
}
```

If the guard returns `false`, the event is skipped for that participant but the subscription remains active for future evaluation.

Both expressions use the same participant vocabulary as rule conditions: the `participant.counter/tag/attribute.<name>` shorthand, `program.counter/tag/attribute.<name>` accessors, the `groups` list, and participant identity fields (`participant.id`, `participant.external_id`, `participant.status`, `participant.enrolled_at`, `participant.created_at`).

Two differences from rule conditions:

* `now` is the worker's wall-clock time when the expression runs, not an event timestamp. There is no event here.
* `event.*` references and balance references (`participant.balance.<symbol>`, `participant.balances`) are rejected with a `400` when the automation is saved.

Create and update responses include a non-blocking `warnings` array for expression issues that do not block saving, including `deprecated_alias` when an expression uses the legacy `state` variable instead of `participant`. Execution stats also report `participants_skipped_error`, the per-run count of participants skipped because the filter or guard expression errored for them.

### Subscriptions

Each qualifying participant gets a subscription: the per-participant record of when their event should fire. A birthday automation with three qualifying participants creates three subscriptions, each with a different `next_trigger_at` based on that participant's `birthday` attribute.

Scrip periodically re-evaluates the automation to pick up new participants and drop those who no longer match. You can also trigger a re-evaluation manually:

```bash theme={null}
POST /v1/programs/{programId}/automations/{automationId}/refresh-subscriptions
```

To inspect individual subscriptions:

```bash theme={null}
GET /v1/programs/{programId}/automations/{automationId}/subscriptions
```

## Lifecycle

Every automation has a `status` that controls whether it can fire:

| Status      | Meaning                                                                                                                                                                                                                                             |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active`    | Ready to fire on schedule. Can be triggered, paused, or archived.                                                                                                                                                                                   |
| `paused`    | Will not fire until reactivated via `PATCH` with `status: active`.                                                                                                                                                                                  |
| `completed` | One-time and immediate automations move here after firing. Terminal.                                                                                                                                                                                |
| `failed`    | Disabled by a terminal error: an expression that can no longer be scheduled, or a one-time execution that failed. Consecutive transient failures are tracked in `last_error` and `consecutive_failures` without disabling the automation. Terminal. |
| `archived`  | Soft-deleted via the delete endpoint, or by canceling a program-scoped one-time. Terminal.                                                                                                                                                          |

### Status transitions

```mermaid theme={null}
stateDiagram-v2
    [*] --> active
    active --> paused : pause
    paused --> active : reactivate
    active --> completed : fires (one-time / immediate)
    active --> failed : terminal error
    active --> archived : delete or cancel
    paused --> archived : delete
```

Only `active` and `paused` are reversible. The other three statuses are terminal. To re-run a completed one-time automation, create a new one.

### Execution status (participant-scoped only)

Participant-scoped automations track fan-out progress with a separate `execution_status`. This is distinct from `status` because a recurring automation stays `active` across multiple fan-out cycles, while `execution_status` tracks the current cycle:

| Execution Status | Meaning                                                                     |
| ---------------- | --------------------------------------------------------------------------- |
| `idle`           | Not running. Ready to be triggered or picked up by the scheduler.           |
| `pending`        | Queued for fan-out.                                                         |
| `executing`      | Fan-out in progress.                                                        |
| `completed`      | Fan-out finished. Resets to `idle` for recurring automations.               |
| `failed`         | Fan-out failed or was canceled. Resets to `idle` for recurring automations. |

Manual trigger requires `execution_status` to be `idle`. If a fan-out is already running, wait for it to complete or cancel it first.

```json theme={null}
{
  "status": "active",
  "execution_status": "executing",
  "participants_total": 12500,
  "participants_processed": 8340
}
```

### Cancel behavior

Canceling a participant-scoped fan-out sets `execution_status` to `failed`. Participants already processed keep their events; remaining participants are skipped. The automation stays `active` and fires again on its next schedule.

Canceling a program-scoped one-time archives the automation. Since `archived` is terminal, the automation cannot be triggered afterward.

## Rule-Created Automations

The `SCHEDULE_EVENT` and `BROADCAST` rule actions create automations automatically. These appear with `source: rule_action` and are deduplicated across event replays. See [Rule Actions](/guides/rule-actions#scheduling-actions) for details.

## Managing Automations

List, update, trigger, and delete automations through the API. The list endpoint supports filtering by `trigger_type`, `scope`, `status`, and `source` (`api` or `rule_action`).

```bash theme={null}
GET    /v1/programs/{programId}/automations
GET    /v1/programs/{programId}/automations/{automationId}
PATCH  /v1/programs/{programId}/automations/{automationId}
DELETE /v1/programs/{programId}/automations/{automationId}
POST   /v1/programs/{programId}/automations/{automationId}/trigger
POST   /v1/programs/{programId}/automations/{automationId}/cancel
POST   /v1/programs/{programId}/automations/{automationId}/refresh-subscriptions
```
