> ## 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.

# Core Concepts

> How programs, assets, participants, events, rules, and the double-entry ledger fit together

Scrip is built around six concepts: programs, assets, participants, events, rules, and a double-entry ledger. The table below summarizes each one, and the end-to-end example at the bottom shows how they connect.

| Concept         | What it is                                              | Guide                                 |
| --------------- | ------------------------------------------------------- | ------------------------------------- |
| **Program**     | Container for rules, assets, and participants           | [Programs](/guides/programs)          |
| **Asset**       | Unit of value: points, cashback, nights, credits        | [Assets](/guides/asset-configuration) |
| **Participant** | A user in your system, identified by your `external_id` | [Participants](/guides/participants)  |
| **Event**       | A signal from your app that triggers rule evaluation    | [Events](/guides/event-processing)    |
| **Rule**        | A CEL condition and a list of actions                   | [Rules](/guides/writing-rules)        |
| **Ledger**      | Double-entry record of every balance change             | [Ledger](/guides/ledger)              |

## Programs

A program is the top-level container. It holds your rules, links to your assets, and scopes your participants. Most teams create one program per use case: "Customer Loyalty," "Referral Rewards," "Driver Incentives."

Events are always sent to a specific program, and rules belong to exactly one program.

## Assets

An asset is the unit of value you're tracking. Points, cashback dollars, nights stayed, referral credits. You configure how each asset behaves:

* **Inventory mode:** `SIMPLE` tracks a single aggregate balance. `LOT` tracks each credit individually with its own expiration, maturity, issuer lineage, and oldest-first spending.
* **Issuance policy:** `UNLIMITED` mints new value on every credit (no cap). `PREFUNDED` draws from a fixed program wallet, so credits fail when the wallet is empty. Use `PREFUNDED` when you need to enforce a budget.

## Participants

Participants are your users. You identify them with your own `external_id` so they stay in sync with your application. Participants can be created explicitly or automatically on first event.

Each participant carries state that rules can read and write:

* **Tags:** Boolean flags like `vip` or `first_purchase`.
* **Counters:** Numeric values like `lifetime_spend` or `purchase_count`.
* **Attributes:** Key-value strings like `region: "US"`.

## Events

Events are the inputs. Your application sends one whenever something happens that might affect a participant: a purchase, a signup, a referral, a cancellation.

```json theme={null}
{
  "program_id": "...",
  "external_id": "user_123",
  "idempotency_key": "order-456-completed",
  "event_timestamp": "2025-01-15T10:30:00Z",
  "event_data": {
    "type": "purchase",
    "amount": 105.00
  }
}
```

Events process asynchronously. The API confirms receipt and a worker evaluates rules in the background.

The `idempotency_key` ensures exactly-once event processing: if you retry with the same key, you get back the same event identity instead of processing it again. To correct or replace an event, send a new event with a new idempotency key.

## Rules

A rule is a condition and a list of actions. The condition is a [CEL](/guides/cel-expressions) expression (Common Expression Language, a lightweight expression syntax) evaluated against the event and the participant's current state. When it returns true, the actions fire.

```json theme={null}
{
  "name": "Cashback on large purchases",
  "condition": "event.type == 'purchase' && event.amount >= 100.0",
  "actions": [
    {"type": "CREDIT", "asset_id": "...", "amount": "10"}
  ]
}
```

Rules evaluate in order. Multiple rules can fire on the same event. If you want only the first matching rule to fire, set `stop_after_match: true` on that rule to skip the remaining rules.

## The Ledger

Every balance change is recorded as a journal entry with two sides: a debit and a credit. When a participant earns points, the credit goes to the participant and the corresponding debit comes from a source account: the system issuance account for `UNLIMITED` assets, or the program wallet for `PREFUNDED`. Nothing is ever mutated in place.

This gives you a complete audit trail: you can trace any balance back to the event and rule that created it.

## End-to-End Example

Using the "Cashback on large purchases" rule from above, here's what happens when a \$105 purchase comes in:

<Steps>
  <Step title="Your app sends an event">
    `user_123` made a \$105 purchase. You send it to Scrip with `external_id: "user_123"` and `event_data: {type: "purchase", amount: 105.00}`.
  </Step>

  <Step title="The engine loads the participant">
    Scrip looks up the participant matching `user_123` and loads their current state: tags, counters, and attributes.
  </Step>

  <Step title="Rules evaluate in order">
    The condition `event.type == 'purchase' && event.amount >= 100.0` is checked. 105 >= 100, so it matches.
  </Step>

  <Step title="The CREDIT action fires">
    \$10 is credited to the participant's balance.
  </Step>

  <Step title="The ledger records it">
    A journal entry credits `user_123` by \$10 and debits the source account by \$10 (the system issuance account for an `UNLIMITED` asset, or the program wallet for `PREFUNDED`).
  </Step>

  <Step title="Result">
    `user_123` now has \$10.00 available to redeem.
  </Step>
</Steps>

The [Quickstart](/quickstart) walks through this exact flow with real API calls.
