Skip to main content
An automation stores the event_data you would send to POST /v1/events and submits it when its trigger fires, either once to the program or individually to each matching participant. The event then enters the rules engine like any other event. Scrip adds nothing to it: the event_data you store is exactly the event_data your rules see. If you want rules to match on event.type, set type inside event_data yourself, exactly as you do on every other event. An automation that stores:
fires an event that this rule condition matches:
Events fired by automations are system events (event_type: SYSTEM). Their event_timestamp is the scheduled trigger time, even if processing starts later. Each firing reuses its idempotency key on retries to prevent duplicate events. Use automations for monthly bonuses, one-time promotions, birthday rewards, or any event that should happen without an API call from your application.

Anatomy

Every automation combines four things: a name, a trigger (when it fires), a scope (who receives the event), and the event_data it submits.
The trigger object has the same shape in create requests, update requests, and responses: Not every trigger and scope combination is valid:

Trigger types

Cron

A cron automation fires on a recurring schedule.
A cron automation stays active across firings. Transient failures do not disable it; it records last_error and fires again on schedule. Only a terminal error, such as a cron expression that can no longer be scheduled, sets status: failed (see lifecycle). Updating cron_expression or timezone recomputes next_run_at in the same request.
A scheduled reset and a purchase can run in either order. A purchase near midnight on the 1st may therefore count toward either month, even though the reset’s event_timestamp is the scheduled time. For exact calendar totals, have your backend calculate the period’s total or count and include it on each event. See Processing order is not guaranteed and the spend threshold pattern.

One-time

A one-time automation fires once, at trigger_at or as soon as possible when trigger_at is omitted, then moves to completed.
Omitting trigger_at means “now”: the automation fires as soon as Scrip picks it up. A one-time program-scoped automation with no trigger_at is the way to push a one-off event to the program without waiting. The BROADCAST rule action creates exactly this shape with participants scope; see Rule Actions. To reschedule a pending one-time automation, PATCH its trigger.trigger_at. Setting it to null makes it fire as soon as possible. For a worked one-time example paired with the rule that consumes its event, see the one-time bonus blast.

Participant state

A participant-state automation fires per participant, when that participant’s own state says it is time. Each qualifying participant gets a subscription with an independent schedule. Participant-state automations always use participants scope.
The schedule_type determines how each participant’s trigger time is computed.
Trigger types use lowercase (cron, one_time, 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.
This triggers 7 days before each participant’s birthday (negative offset). A participant with birthday: "03-15" would trigger on March 8th each year. 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.
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.
THRESHOLD triggers when a participant’s counter crosses a value. The delay defers the event after the condition is met, giving the participant time to take further action before it fires.
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.

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 queue a re-evaluation with the refresh subscriptions endpoint, and inspect individual subscriptions with the subscriptions listing:
A subscription’s status is one of active, firing, paused, completed, failed, or cancelled, and the listing accepts a status filter with the same values. For a worked participant-state example paired with the rule that consumes its event, see the birthday reward.

Scopes and fan-out

The scope decides who receives the event when the trigger fires. program submits a single event to the program. Rules evaluate once, without a participant context, which fits program-level work such as resetting program counters. For one_time automations you can add participant_id to deliver that single event to one specific participant instead. participants delivers the firing as an individual event to every matching participant, which these docs call fan-out. Each participant’s event evaluates rules against that participant’s own state, exactly as if you had sent one event per participant yourself. participant_filter selects who matches; with no filter, every active participant in the program receives the event. Every fan-out event is a system event carrying the same scheduled trigger time as its event_timestamp and a deterministic idempotency key that includes the participant, so one firing produces exactly one event per participant even across retries. While a fan-out runs, the automation reports progress:
participants_skipped_error counts participants skipped because their filter or guard expression errored for them.

Filters and guards

participant_filter and guard_condition apply to every participants-scoped automation (cron, one-time, and participant state). The two optional CEL expressions run at different phases. participant_filter runs during evaluation and controls who is in. A birthday automation only makes sense for participants who have a birthday attribute:
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:
If the guard returns false, the event is skipped for that participant. For the recurring schedule types (ATTRIBUTE_DATE, INTERVAL, CRON) the subscription remains active for future firings. For the one-shot THRESHOLD type the subscription is cancelled, and a later re-evaluation re-subscribes the participant only if they still qualify. 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 deprecated state variable instead of participant. filter_hints narrow the candidate set with indexed lookups before any CEL runs, which matters for large programs. Each hint names a type (has_tag, has_attribute, or has_counter) and a value (the tag, attribute key, or counter key):
Hints are an optimization, not a second filter: pair them with a participant_filter that states the actual condition.

Lifecycle

An automation carries two status fields that answer different questions. status describes the automation definition: may it fire at all. execution_status, present on participants-scoped automations, describes the current fan-out run: is one in progress right now. The two fields overlap in vocabulary but not in meaning. status: completed is forever: the one-time automation has done its job. execution_status: completed only closes one cycle of a recurring automation. Likewise status: failed means the definition is broken and needs your intervention, while execution_status: failed means one run went wrong and the next scheduled firing starts fresh. Transient failures never change status: the automation records last_error and keeps firing on schedule. By default the list endpoint hides terminal statuses (completed, failed, archived); filter by status explicitly to see them. A failed automation therefore disappears from the default list even though it is recoverable.

Cancel behavior

Cancelling an executing 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. Cancelling a program-scoped one-time before it fires archives the automation.

Testing an automation

Because event_data is emitted verbatim, testing an automation means testing two things you can check separately: what your rules do with that event_data, and whether the trigger and filters select the moments and participants you intend. For the first, send the automation’s event_data as a scenario event in a rule configuration simulation. The simulation shows which rules match, the amounts they calculate, and how balances would change. Live balances and state stay unchanged. See Testing for the workflow. For a real end-to-end test, create a one-time copy of the automation: same event_data, trigger.type: one_time with trigger_at omitted, and a participant_filter that selects a test participant:
The copy fires immediately, delivers one real event to the test participant, and moves to completed. Verify the outcome the same way you verify any processed event: fetch the event, read rule_evaluations, and check the participant’s balance.

Managing automations

Updates go through PATCH with the same nested trigger object as create. The trigger must support the chosen scope. Scrip rejects scheduling fields that do not belong to that trigger type. The list endpoint filters by trigger (the trigger.type), scope, status, and source. Every automation carries a source: The default list returns api and rule_action rows only. Pass source=system to inspect automations Scrip creates, for example when investigating a tier expiration.