Documentation Index
Fetch the complete documentation index at: https://docs.scrip.dev/llms.txt
Use this file to discover all available pages before exploring further.
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}",
"email": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe"
}'
| 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 |
email | No | Contact email (validated) |
phone | No | Contact phone number |
first_name | No | First name |
last_name | No | Last name |
display_name | No | Display name |
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.
Profile Fields
Participants support optional profile fields for contact and display information: email, phone, first_name, last_name, and display_name. These are returned in all participant responses (create, update, get, list).
Profile fields follow a three-state convention:
| Value sent | Behavior |
|---|
Omitted (or null) | Existing value is preserved |
| Non-empty string | Field is set to the new value |
Empty string ("") | Field is cleared |
The email field is validated on both create and update. Invalid addresses return a 400 error.
# Update profile fields
curl -X PATCH https://api.scrip.dev/v1/participants/{id} \
-H "Authorization: Bearer $SCRIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"display_name": "Jane Doe"
}'
# Clear a field by sending an empty string
curl -X PATCH https://api.scrip.dev/v1/participants/{id} \
-H "Authorization: Bearer $SCRIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": ""
}'
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 identifiers, status, and profile fields. 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, program_ids, and profile fields 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 |
Balance and counter actions are blocked because a suspended or closed participant should not accumulate value. Metadata actions (tags, attributes, tiers) 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 three buckets:
| Bucket | Description |
|---|
AVAILABLE | Spendable immediately |
HELD | Reserved for authorization holds, pending settlements, or fraud review |
DEFERRED | Not yet matured. Credits with a future matures_at land here and move to AVAILABLE automatically when the date passes. |
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.