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}"
}'
| Field | Required | Description |
|---|
external_id | Yes | Your application’s user identifier |
program_id | No | Enroll the participant in a program on creation |
status | No | ACTIVE (default), SUSPENDED, or CLOSED |
attributes | No | Key-value metadata |
tags | No | List 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:
| ID | Format | Use |
|---|
id | UUID | Scrip’s internal identifier, used in all API paths |
external_id | String | Your 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:
| Status | Description |
|---|
ACTIVE | Normal operation. All actions allowed. |
SUSPENDED | Temporary freeze (e.g., fraud review, compliance hold). Reversible |
CLOSED | Deactivated. Can be transitioned back to ACTIVE or SUSPENDED |
What’s allowed by status
| Action | ACTIVE | SUSPENDED / CLOSED |
|---|
CREDIT | Yes | Blocked |
DEBIT | Yes | Blocked |
HOLD | Yes | Blocked |
RELEASE | Yes | Blocked |
FORFEIT | Yes | Blocked |
COUNTER | Yes | Blocked |
TAG / UNTAG | Yes | Allowed |
SET_ATTRIBUTE | Yes | Allowed |
SET_TIER | Yes | Allowed |
BROADCAST | Yes | Allowed |
SCHEDULE_EVENT | Yes | Allowed |
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:
| Bucket | Description |
|---|
AVAILABLE | Spendable immediately |
HELD | Reserved 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):
| Filter | Filters on | Use case |
|---|
from / to | created_at (system ingestion time) | When the journal entry was recorded |
event_from / event_to | event_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:
| Type | Purpose | Example |
|---|
| Tags | Boolean flags | vip, first_purchase |
| Counters | Numeric trackers | purchase_count: 42 |
| Attributes | Key-value strings | region: "US" |
| Tiers | Status levels with benefits | loyalty_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.