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.
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 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 and last_error for observability. After repeated consecutive failures, the automation is disabled with status failed.
One-Time Automations
One-time automations fire a single event at a specific timestamp, then transition to completed.
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 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.
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.
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.
Trigger types use lowercase (cron, one_time, immediate, participant_state), while schedule types use uppercase (ATTRIBUTE_DATE, INTERVAL, CRON, THRESHOLD).
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.
{
"schedule_type": "ATTRIBUTE_DATE",
"schedule_config": {
"attribute_key": "birthday",
"date_format": "MM-DD",
"offset": "-168h"
}
}
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. 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.
{
"schedule_type": "INTERVAL",
"schedule_config": {
"interval": "720h"
}
}
| Field | Description |
|---|
interval | Duration (e.g., "720h" for 30 days, "24h" for 1 day) |
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.
{
"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.
{
"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 |
Filters and Guards
Participant state automations run in two phases, each with an optional CEL expression.
participant_filter runs during evaluation and controls which participants get subscriptions. A birthday automation only makes sense for participants who have a birthday attribute:
{
"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:
{
"guard_condition": "get(participant.counters, 'lifetime_spend', 0.0) >= 1000.0"
}
If the guard returns false, the event is skipped for that participant but the subscription remains active for future evaluation.
Neither field can reference event.* since there is no event context during automation evaluation.
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:
POST /v1/programs/{programId}/automations/{automationId}/refresh-subscriptions
To inspect individual subscriptions:
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 after repeated consecutive failures. Terminal. |
archived | Soft-deleted via the delete endpoint, or by canceling a program-scoped one-time. Terminal. |
Status transitions
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:
| 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.
{
"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 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).
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