Skip to main content
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 TypeDescription
cronFires on a recurring schedule (e.g., first of every month)
one_timeFires once at a specific timestamp
immediateFires as soon as the automation is created
participant_stateFires when a participant’s state matches a condition
ScopeDescription
programSends a single event to the program. Rules evaluate once.
participantsFans out an event to every matching participant. Each event evaluates rules against that participant’s state.
Not all combinations are valid:
programparticipants
cronYesYes
one_timeYesYes
immediateNoYes
participant_stateNoYes
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"
}
FieldRequiredDescription
nameYesDisplay name (unique per program)
trigger.typeYescron
trigger.cron_expressionYesStandard cron expression. Inline timezone prefixes (CRON_TZ=, TZ=) are not allowed; use timezone instead.
trigger.timezoneNotz database timezone (e.g., America/New_York). Defaults to UTC.
scopeYesprogram or participants
event_nameYesThe event type sent when the automation fires
payloadNoJSON 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.
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}
}
FieldRequiredDescription
trigger.trigger_atYesRFC 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.
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": "-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.
FieldDescription
attribute_keyParticipant attribute containing the date value
date_formatMM-DD (recurring annual) or YYYY-MM-DD (one-time)
offsetOptional 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.
{
  "schedule_type": "INTERVAL",
  "schedule_config": {
    "interval": "30d"
  }
}
FieldDescription
intervalDuration 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.
{
  "schedule_type": "CRON",
  "schedule_config": {
    "expression": "0 9 * * 1"
  }
}
FieldDescription
expressionStandard 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.
FieldDescription
counter_keyCounter to evaluate
operator>=, >, ==, <=, <
thresholdNumeric value to compare against
delayDuration 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:
{
  "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": "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:
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:
StatusMeaning
activeReady to fire on schedule. Can be triggered, paused, or archived.
pausedWill not fire until reactivated via PATCH with status: active.
completedOne-time and immediate automations move here after firing. Terminal.
failedDisabled 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.
archivedSoft-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. This is distinct from status because a recurring automation stays active across multiple fan-out cycles, while execution_status tracks the current cycle:
Execution StatusMeaning
idleNot running. Ready to be triggered or picked up by the scheduler.
pendingQueued for fan-out.
executingFan-out in progress.
completedFan-out finished. Resets to idle for recurring automations.
failedFan-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