Participant Status Restrictions
Not all actions are allowed on every participant. Financial actions are blocked forSUSPENDED and CLOSED participants to prevent inactive accounts from accumulating value. Metadata actions are always allowed so you can still manage inactive accounts (e.g., tagging for audit, updating attributes during a review).
If a rule triggers a blocked action, the action fails and the event is marked
FAILED. If matching rules only trigger allowed actions, the event completes normally. See Participants: What’s allowed by status for the full matrix.
Action Types
Dynamic action fields
Action string fields use${{ ... }} for CEL expressions, and plain values are literals. One exception: target external_id and participant_id evaluate unwrapped values as CEL, so always wrap expressions in ${{ }} and write fixed IDs as quoted string expressions.
Asset Actions
CREDIT
Add funds to an account.
For
UNLIMITED assets, CREDIT mints new funds. For PREFUNDED assets, CREDIT draws from the program wallet.
Automatic rounding: If the evaluated amount has more decimal places than the asset’s
scale, the value is rounded automatically. For example, ${{ event.amount * 0.03 }} might produce 1.009, which rounds to "1.01" on a scale: 2 asset. No error is returned. Use round() in your CEL expression if you need explicit control.DEBIT
Remove funds from an account.
Fails with
insufficient balance if the available balance is less than the requested amount, unless allow_negative is true. See Balance Operations: Negative Balances for details and use cases.
HOLD
Reserve funds by moving them fromAVAILABLE to HELD.
Held funds are not spendable. Use for authorization holds or pending settlements. When
reference_id is provided, the held lots are stamped so a future RELEASE can target them by reference.
RELEASE
Move funds fromHELD back to AVAILABLE.
FORFEIT
Remove funds permanently. Debits the participant and creditsSYSTEM_BREAKAGE.
Use for point expiration or policy violations. Rule-triggered forfeits are blocked for non-active participants. Use the forfeit API endpoint for manual cleanup of closed accounts.
VOID_HOLD
Cancel HELD lots that were credited directly into the HELD bucket (not moved there from AVAILABLE via a HOLD action). Returns value to the original source account (program wallet forPREFUNDED, SYSTEM_ISSUANCE for UNLIMITED).
Use for auth reversals, when a merchant voids a transaction before settlement. Lots that a participant already owned and moved to HELD via a HOLD action are excluded, so you cannot accidentally void participant-owned funds.
amount, bucket, and lot_ids are not supported: the entire provisional accrual is voided.
State Actions
State actions are durable updates, not same-event condition inputs. If a rule adds a tag, increments a counter, sets an attribute, or assigns a tier, the new state is visible to later events. It is not visible to lower-order rule conditions or to dynamic action expressions (like
amount, COUNTER value, or SET_ATTRIBUTE value) in the current event. See State Snapshot Evaluation Behavior.TAG
Add a boolean flag to an entity.UNTAG
Remove a boolean flag from an entity.
Removing a tag that doesn’t exist is a no-op. The action succeeds silently.
COUNTER
Increment a numeric value.
The
value field increments the counter. It does not replace it. See State Management for details on auto-reset behavior.
SET_ATTRIBUTE
Set a key-value pair on an entity.
Use a plain string for literals (
"high") and ${{ ... }} for dynamic values ("${{ event.category }}").
SET_TIER
Assign a tier level on an entity. Eachtier represents a separate track (e.g., "status", "loyalty"), and level specifies a position within that track. Tier levels have a numeric rank that defines their order in the hierarchy.
If the participant already holds a tier in the same track,
SET_TIER overwrites it. The previous tier is recorded as a transition for audit purposes.
This rule promotes a participant to gold when their lifetime spend crosses $1,000:
participant.tiers. Each entry is a map with level (string), rank (number), benefits (map), acquired (timestamp string), and expires (timestamp string or null).
expires_at is set, the system schedules a tier_expiration event when the duration elapses. That event enters the rules engine and triggers the tier’s configured downgrade policy.
Scheduling Actions
These actions create automations under the hood.SCHEDULE_EVENT creates a one-time automation scoped to the program, and BROADCAST creates an immediate automation that fans out to all participants.
SCHEDULE_EVENT
Create a follow-up event that fires after a specified delay. Under the hood, this creates aone_time + program automation targeting the same participant who triggered the original rule.
Common durations:
1d or 24h (1 day), 7d (1 week), 30d (30 days), 365d (1 year).
This rule grants a signup bonus and schedules an inactivity check 30 days later:
Payload templates
String values inpayload may embed ${{ }} templates at any nesting depth. Templates resolve when the triggering rule fires, against the triggering event’s context. A whole-string template keeps the expression’s type (numbers arrive as JSON numbers); mixed text and template stringifies the result. Object keys are never evaluated. A template that errors at runtime fails the whole event, the same as a bad amount expression.
A whole-string template that evaluates to CEL null becomes JSON null; in mixed text, null contributes an empty string. Numeric results must be finite and within the precision-safe range (magnitude at or below 2^53). Results outside that range, NaN, and non-JSON-encodable values fail the action with an error naming the payload field path.
projected_points arrives in the scheduled event as a JSON number, and label as a concatenated string.
BROADCAST
Fan out an event to every active participant in the program. Under the hood, this creates animmediate + participants automation that begins fan-out right away.
BROADCAST cannot include target, asset_id, amount, or other action-specific fields. The broadcast event itself triggers rules, and those rules define what happens.
This pair of rules runs a conditional monthly bonus. The first rule fires the broadcast; the second rule runs for each participant who qualifies:
Payload templates
payload string values accept the same ${{ }} templates as SCHEDULE_EVENT. Templates resolve once, when the triggering rule fires, against the triggering event’s context, not per recipient: every participant receives the same resolved payload. A template that errors at runtime fails the whole event.
BROADCAST and SCHEDULE_EVENT actions are skipped in test mode. Simulation and test runs still resolve their payload templates. The action result shows the payload that would be emitted or identifies the payload field whose template failed.Targeting
By default, actions apply to the event’s participant. Usetarget to route an action to a different entity.
These action types support target: CREDIT, DEBIT, TAG, UNTAG, COUNTER, SET_ATTRIBUTE, SET_TIER.
Static Targets
Target the program itself or a specific group by ID:Dynamic Targets
Resolve the target from event data using a CEL expression. Useexternal_id to look up a participant by your application’s user ID, or participant_id to look up by Scrip UUID.
Reference a field from event data (most common):
event data and now, not participant state or program state. Only one ID field (external_id, participant_id, or id) can be specified per target. The target participant must exist and be enrolled in the program.
Example: Referral Bonus
Credit both the new user and their referrer from a single event:Lot Expiration and Vesting
ForLOT-mode assets, CREDIT actions can set expiration and vesting dates:
${{ }} expression that resolves to either form. Durations count from processing time and use the Go units (s, m, h, and smaller) plus d (24 hours) and w (168 hours), combinable ("1w2d12h") with fractional values allowed; months and years are rejected. For occurrence-based or exact calendar deadlines, compute an RFC 3339 timestamp in your backend and resolve it from event_data. The same units work in SET_TIER expires_at, SCHEDULE_EVENT delay, COUNTER reset_after, and budget interval. The same format applies to tier lifecycle retention.duration and automation schedule_config durations. Expiration and vesting fields are ignored for SIMPLE-mode assets.
See Lots & Expiration for details on lot lifecycle.