Skip to main content
A participant represents a user in your system. You identify participants with your own external IDs, and Scrip handles enrollment, balances, and state tracking.

Creating Participants

Explicitly via the API

curl -X POST https://api.scrip.dev/v1/participants \
  -H "Authorization: Bearer $SCRIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "user_123",
    "program_id": "{program_id}"
  }'
FieldRequiredDescription
external_idYesYour application’s user identifier
program_idNoEnroll the participant in a program on creation
statusNoACTIVE (default), SUSPENDED, or CLOSED
attributesNoKey-value metadata
tagsNoList of string labels
If a participant with the same external_id already exists, the call upserts: it updates the existing participant instead of creating a duplicate.

Automatically on first event

When a program’s on_unknown_participant is set to CREATE (the default), participants are created the first time you send an event with an unrecognized external_id:
curl -X POST https://api.scrip.dev/v1/events \
  -H "Authorization: Bearer $SCRIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": "{program_id}",
    "external_id": "user_456",
    "idempotency_key": "signup-user-456",
    "event_timestamp": "2025-01-15T10:30:00Z",
    "event_data": {"type": "signup"}
  }'
Scrip creates the participant, enrolls them in the program, and processes the event in one step. Set on_unknown_participant to REJECT if you want to require explicit creation before sending events. See Programs for details.

Auto-enrollment of existing participants

When an event targets a participant who exists but isn’t enrolled in the event’s program, Scrip automatically enrolls them. This applies across all identity paths (external_id, participant_id, recipient_id, recipient_external_id). Inactive enrollments (FROZEN, LOCKED, or CLOSED) are reactivated. The on_unknown_participant setting only controls whether new participants are created. It does not affect enrollment of existing ones.

Identifiers

Participants have two IDs:
IDFormatUse
idUUIDScrip’s internal identifier, used in all API paths
external_idStringYour application’s user ID, used for lookups
To find a participant by your external ID:
GET /v1/participants?external_id=user_123
Use the returned id for all subsequent API calls. The list endpoint returns a slim response. To get the full participant state in one call, use the detail endpoint:
GET /v1/participants/{id}
The detail response includes balances, tags, counters, attributes, tiers, and program_ids inline.

Status

A participant’s status controls what operations are allowed:
StatusDescription
ACTIVENormal operation. All actions allowed.
SUSPENDEDTemporary freeze (e.g., fraud review, compliance hold). Reversible
CLOSEDDeactivated. Can be transitioned back to ACTIVE or SUSPENDED

What’s allowed by status

ActionACTIVESUSPENDED / CLOSED
CREDITYesBlocked
DEBITYesBlocked
HOLDYesBlocked
RELEASEYesBlocked
FORFEITYesBlocked
COUNTERYesBlocked
TAG / UNTAGYesAllowed
SET_ATTRIBUTEYesAllowed
SET_TIERYesAllowed
BROADCASTYesAllowed
SCHEDULE_EVENTYesAllowed
Financial actions (anything that moves balances or increments counters) are blocked because a suspended or closed participant should not accumulate value. Metadata actions are allowed because you still need to manage inactive accounts: tagging a participant with fraud_confirmed, setting attributes for audit trails, or adjusting tiers during a review period.

How this affects events

Events targeting suspended or closed participants are still accepted (202) and processed normally. The outcome depends on which actions the matching rules trigger:
  • If a rule triggers a blocked action (e.g., CREDIT), the action fails and the event is marked FAILED.
  • If matching rules only trigger allowed actions (e.g., TAG, SET_ATTRIBUTE), those actions execute and the event is marked COMPLETED.
  • If no rules match, the event is marked COMPLETED with no actions taken.
If you need events to fail unconditionally for inactive participants, add a CEL guard condition to your rules: participant.status == "ACTIVE". This causes the rule not to match, so no actions execute and the event completes with no effect.
PATCH /v1/participants/{id}/status
{"status": "SUSPENDED"}

Balances

Check a participant’s current balances across all assets:
GET /v1/participants/{id}/balances
Balances are split by asset into two buckets:
BucketDescription
AVAILABLESpendable immediately
HELDReserved for authorization holds, pending settlements, or fraud review
You can perform manual balance adjustments directly on a participant:
POST /v1/participants/{id}/balances/adjust
{
  "program_id": "{program_id}",
  "asset_id": "{asset_id}",
  "type": "CREDIT",
  "amount": "500",
  "description": "Customer service goodwill credit"
}
See Balance Operations for hold, release, forfeit, and other operations.

Transaction History

View the ledger entries for a participant:
GET /v1/participants/{id}/activity/history
Returns a chronological list of credits, debits, holds, and releases with journal entry details.

Time-Range Filters

The endpoint supports two independent time-range filters that can be combined (AND semantics):
FilterFilters onUse case
from / tocreated_at (system ingestion time)When the journal entry was recorded
event_from / event_toevent_timestamp (event occurrence time)When the originating event occurred in your system
Entries without an originating event fall back to created_at for event_from/event_to filtering. You can also retrieve the events processed for a participant:
GET /v1/participants/{id}/activity/events

Participant State

Each participant carries state that rules can read and update:
TypePurposeExample
TagsBoolean flagsvip, first_purchase
CountersNumeric trackerspurchase_count: 42
AttributesKey-value stringsregion: "US"
TiersStatus levels with benefitsloyalty_tier: "gold"
Tags are normalized to lowercase. Counters support high-precision numerics. See State Management for how to read, update, and use state in rules.