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 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}
}
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": "-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.
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. 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"
  }
}
FieldDescription
intervalDuration (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"
  }
}
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

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:
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 after repeated consecutive failures. 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:
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