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

# Create a rule

> Create a rule that evaluates a CEL condition against incoming events and executes actions when matched.

Creates a rule within a program. A rule consists of a CEL `condition` that is evaluated against each incoming event and an `actions` array that executes when the condition matches. Use `description` to add a human-readable summary of what the rule does. Common actions include crediting balances, incrementing counters, and setting tags.

The `order` field controls evaluation priority. Lower values are evaluated first. If omitted, `order` is auto-assigned above the current highest value in the program. No two active rules in the same program can share the same `order`. Leave gaps between values (e.g. 10, 20, 30) so you can insert new rules without reordering.

Set `stop_after_match` to `true` to prevent lower-priority rules from firing when this rule matches.

`active_from` and `active_to` define an optional time window using RFC 3339 timestamps. The window is checked against the event's `event_timestamp` (the same value CEL sees as `now`), not the wall-clock time at processing. `active_from` is inclusive, `active_to` is exclusive. A matching rule outside the window records an `OUTSIDE_TIME_WINDOW` skipped evaluation. Event timestamps are caller-supplied, so historical imports with an `event_timestamp` inside a past window do fire time-windowed rules. Use these to layer promotional rules on top of permanent base rules.

`budgets` cap how much a rule can issue per asset over a given period. Each budget specifies an `asset_id`, a `limit`, and an optional `schedule_type` (`CRON` or `INTERVAL`) that controls automatic resets. Omitting the schedule type creates a lifetime budget that never resets on its own. When a budget is exhausted, the rule is skipped entirely: all of its actions are rolled back and the evaluation is recorded as `skipped` with a `budget_exceeded` reason. You can manually reset a budget via the [reset budget endpoint](/api-reference/rules/reset-a-rule-budget).

A rule is created in `ACTIVE` status by default. You can also set it to `SUSPENDED` at creation. Rules cannot be created under an archived program.

<Note>
  For usage patterns and examples, see the [Writing Rules guide](/guides/writing-rules).
</Note>


## OpenAPI

````yaml POST /v1/rules
openapi: 3.0.3
info:
  contact:
    email: support@scrip.dev
    name: Scrip Support
    url: https://scrip.dev/support
  description: >-
    Universal Incentive Infrastructure API for managing loyalty programs,
    rewards, and participant balances.


    Scrip provides a complete backend for building incentive and loyalty
    programs. Core concepts:

    - **Programs**: Containers for incentive logic (e.g., "Q1 Sales Bonus",
    "Customer Loyalty")

    - **Assets**: The currency or points being tracked (e.g., "Bonus Points",
    "Cash Rewards")

    - **Participants**: Users who earn and spend assets (identified by
    external_id)

    - **Groups**: Collections of participants for team-based incentives

    - **Rules**: Automated reward logic triggered by events

    - **Events**: Actions that trigger rule evaluation (e.g., "purchase",
    "referral")


    Response formats:

    - **Collection endpoints** return: {"data": [...], "pagination":
    {"has_more": true, "next_cursor": "..."}}

    - **Single-resource endpoints** return the resource directly

    - **Errors** return: {"code": "...", "message": "...", "details": {...}} —
    `details` is an optional object, present on input errors only
  license:
    name: Proprietary
    url: https://scrip.dev/license
  termsOfService: https://scrip.dev/terms
  title: Scrip API
  version: '1.0'
servers:
  - url: https://api.scrip.dev
security: []
tags:
  - description: >-
      Manage incentive programs. Programs are the top-level container for all
      incentive logic.
    name: Programs
  - description: >-
      Manage asset types (currencies, points). Assets define what participants
      can earn and spend.
    name: Assets
  - description: >-
      Manage participants and their balances. Participants are identified by
      external_id from your system.
    name: Participants
  - description: Manage participant groups for team-based incentives.
    name: Groups
  - description: >-
      Manage automated reward rules. Rules define conditions and actions
      triggered by events.
    name: Rules
  - description: Ingest events that trigger rule evaluation and reward distribution.
    name: Events
  - description: Transfer assets between participants.
    name: Transfers
  - description: Access ledger summaries and program activity reports.
    name: Reporting
  - description: >-
      Redeem participant balances for rewards. Supports raw amount redemptions
      and catalog item redemptions.
    name: Redemptions
  - description: >-
      Manage the reward catalog. Create and manage redeemable items with
      inventory tracking.
    name: Rewards
  - description: >-
      Schedule and manage automated event dispatching. Automations generate
      events on cron schedules, at specific times, or by evaluating participant
      state.
    name: Automations
  - description: >-
      Manage tier types and levels within programs. Tiers define status
      hierarchies that participants progress through based on qualification
      rules.
    name: Tiers
  - description: Inspect double-entry ledger records for auditing and reconciliation.
    name: Journal Entries
  - description: >-
      Manage webhook endpoints and delivery logs. Webhooks notify your
      application of real-time events via HTTP POST with HMAC-SHA256 signatures.
    name: Webhooks
paths:
  /v1/rules:
    post:
      tags:
        - Rules
      summary: Create a rule
      description: >-
        Create a rule that evaluates a CEL condition against incoming events and
        executes actions when matched.
      operationId: createRule
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/handlers.CreateRuleRequest'
        description: Rule with CEL condition and actions array
        required: true
        x-originalParamName: request
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.RuleResponse'
          description: Rule created
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrBadRequestResponse'
          description: >-
            Invalid request, malformed CEL, or invalid action (code:
            validation_error | bad_request)
        '401':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrUnauthorizedResponse'
          description: 'Missing or invalid credentials (code: unauthorized)'
        '403':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrForbiddenResponse'
          description: 'Insufficient permissions (code: forbidden)'
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrNotFoundResponse'
          description: 'Program not found (code: not_found)'
        '409':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrConflictResponse'
          description: 'Order conflict with existing rule (code: conflict)'
        '415':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrUnsupportedMediaTypeResponse'
          description: 'Content-Type must be application/json (code: unsupported_media_type)'
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handlers.ErrInternalResponse'
          description: 'Internal server error (code: internal_error)'
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
components:
  schemas:
    handlers.CreateRuleRequest:
      additionalProperties: false
      description: Request body for creating a new rule with CEL condition and actions
      properties:
        actions:
          description: >-
            Actions to execute when the condition matches (must be non-empty).
            Each action has a `type` field that determines which other fields
            are relevant.
          items:
            $ref: '#/components/schemas/models.RuleAction'
          minItems: 1
          type: array
        active_from:
          description: >-
            Start of the rule's active window (RFC 3339). Null means immediately
            active. Checked against the event's event_timestamp, inclusive.
          example: '2024-01-01T00:00:00Z'
          format: date-time
          type: string
        active_to:
          description: >-
            End of the rule's active window (RFC 3339). Null means no end date.
            Checked against the event's event_timestamp, exclusive.
          example: '2024-12-31T23:59:59Z'
          format: date-time
          type: string
        budgets:
          description: >-
            Optional budget constraints for this rule. Omit or pass null for no
            budgets.
          items:
            $ref: '#/components/schemas/models.BudgetSpec'
          type: array
        condition:
          description: CEL expression that determines when the rule fires
          example: event.type == 'purchase'
          minLength: 1
          type: string
        description:
          description: Human-readable summary of what this rule does
          example: Awards 10 points per dollar spent
          maxLength: 1000
          type: string
        name:
          description: Display name for the rule
          example: Purchase Reward
          maxLength: 255
          minLength: 1
          type: string
        order:
          description: Evaluation sequence (lower = first). Auto-assigned if omitted.
          example: 100
          minimum: 1
          type: integer
        program_id:
          description: Program to attach this rule to
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        status:
          description: Initial lifecycle status (ACTIVE or SUSPENDED). Defaults to ACTIVE.
          enum:
            - ACTIVE
            - SUSPENDED
          example: ACTIVE
          type: string
        stop_after_match:
          description: When true, no subsequent rules evaluate after this one matches
          example: false
          type: boolean
      required:
        - actions
        - condition
        - name
        - program_id
      type: object
    handlers.RuleResponse:
      properties:
        actions:
          description: Actions to execute when the condition matches
          example:
            - amount: ${{ event.amount * 10 }}
              asset_id: 550e8400-e29b-41d4-a716-446655440002
              type: CREDIT
          items:
            type: object
          type: array
        active_from:
          description: >-
            Start of the rule's active window (RFC 3339, null if always active).
            Checked against the event's event_timestamp, inclusive.
          example: '2024-01-01T00:00:00Z'
          format: date-time
          type: string
        active_to:
          description: >-
            End of the rule's active window (RFC 3339, null if no end date).
            Checked against the event's event_timestamp, exclusive.
          example: '2024-12-31T23:59:59Z'
          format: date-time
          type: string
        budgets:
          description: Budget constraints applied to this rule
          items:
            $ref: '#/components/schemas/handlers.BudgetResponse'
          type: array
        condition:
          description: CEL expression that determines when the rule fires
          example: event.type == 'purchase' && event.amount > 0
          type: string
        created_at:
          description: When this rule was created (RFC 3339)
          example: '2024-01-15T10:30:00Z'
          format: date-time
          type: string
        deleted_at:
          description: When this rule was archived (null if not archived)
          example: '2024-06-01T00:00:00Z'
          format: date-time
          type: string
        description:
          description: Human-readable summary of what this rule does
          example: Awards 10 points per dollar spent
          type: string
        id:
          description: Unique identifier for this rule
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        name:
          description: Display name
          example: Purchase Reward
          type: string
        order:
          description: Evaluation sequence (lower = first, must be unique per program)
          example: 100
          type: integer
        program_id:
          description: Program this rule belongs to
          example: 550e8400-e29b-41d4-a716-446655440001
          format: uuid
          type: string
        status:
          description: 'Lifecycle status: ACTIVE, SUSPENDED, or ARCHIVED'
          example: ACTIVE
          type: string
        stop_after_match:
          description: When true, no subsequent rules evaluate after this one matches
          example: false
          type: boolean
        updated_at:
          description: When this rule was last modified (RFC 3339)
          example: '2024-01-15T10:30:00Z'
          format: date-time
          type: string
        warnings:
          description: >-
            Non-blocking advisories about the rule's condition or action
            expressions,

            such as unknown state-key typos or deprecated CEL aliases. Present
            on

            create/update only; never blocks the save.
          items:
            $ref: '#/components/schemas/models.RuleWarning'
          type: array
      type: object
    handlers.ErrBadRequestResponse:
      additionalProperties: false
      properties:
        code:
          description: Code is the machine-readable error code
          example: bad_request
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: >-
            Details provides optional structured error context (field errors,
            etc.)
        message:
          description: Message is the human-readable error description
          example: Invalid request parameters
          type: string
      type: object
    handlers.ErrUnauthorizedResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: unauthorized
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: Details provides optional structured error context
        message:
          description: Message is the human-readable error description
          example: Missing or invalid credentials
          type: string
      type: object
    handlers.ErrForbiddenResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: forbidden
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: Details provides optional structured error context
        message:
          description: Message is the human-readable error description
          example: Insufficient permissions for this action
          type: string
      type: object
    handlers.ErrNotFoundResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: not_found
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: Details provides optional structured error context
        message:
          description: Message is the human-readable error description
          example: Resource not found
          type: string
      type: object
    handlers.ErrConflictResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: conflict
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: Details provides optional structured error context
        message:
          description: Message is the human-readable error description
          example: Resource already exists or state conflict
          type: string
      type: object
    handlers.ErrUnsupportedMediaTypeResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: unsupported_media_type
          type: string
        details:
          allOf:
            - $ref: '#/components/schemas/handlers.ErrorDetails'
          description: Details provides optional structured error context
        message:
          description: Message is the human-readable error description
          example: Content-Type must be application/json
          type: string
      type: object
    handlers.ErrInternalResponse:
      properties:
        code:
          description: Code is the machine-readable error code
          example: internal_error
          type: string
        message:
          description: Message is the human-readable error description
          example: An internal error occurred
          type: string
      type: object
    models.RuleAction:
      properties:
        allow_negative:
          description: >-
            Allow debit to overdraw and create a negative balance when funds are
            insufficient.

            DEBIT only. Defaults to false.
          example: false
          type: boolean
        amount:
          description: >-
            Static numeric string (e.g. "100") or a ${{ }} CEL expression
            returning a number (e.g. "${{ event.amount * 0.03 }}"). Legacy
            unmarked expressions remain supported for stored-rule compatibility
            but are deprecated for new rules.
          example: ${{ event.amount * 0.03 }}
          type: string
        asset_id:
          description: Asset to operate on
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        bucket:
          description: >-
            Balance bucket to target: AVAILABLE or HELD. Defaults to AVAILABLE
            for most actions, HELD for RELEASE.
          example: AVAILABLE
          type: string
        delay:
          description: >-
            How long to wait before firing the scheduled event; h/d/w units
            (e.g. "24h", "7d"). SCHEDULE_EVENT only.
          example: 24h
          type: string
        description:
          description: Context string recorded on the ledger entry
          example: Purchase reward
          type: string
        event_name:
          description: >-
            Event type to inject. For SCHEDULE_EVENT: fires after the delay
            expires. For BROADCAST: fans out to all participants immediately.
          example: check_status
          type: string
        expires_at:
          description: >-
            When the lot/tier expires. CREDIT: lot becomes unusable (LOT-mode
            assets). SET_TIER: tier assignment expires.

            Accepts an RFC3339 timestamp or a duration string with h/d/w units
            (e.g. "365d", "12w", "8760h").
          example: 365d
          format: date-time
          type: string
        key:
          description: Counter or attribute key name
          example: purchase_count
          type: string
        level:
          description: Tier level within the track (e.g. "gold", "platinum")
          example: gold
          type: string
        matures_at:
          description: >-
            When the credited lot becomes usable (CREDIT only, LOT-mode assets).
            Accepts an RFC3339 timestamp or a duration string with h/d/w units
            (e.g. "30d", "720h").
          example: 30d
          format: date-time
          type: string
        payload:
          description: >-
            Additional data included with the event when it fires. String values
            may embed ${{ }} CEL expressions

            (at any nesting depth, values only), resolved against the triggering
            rule's context when the action executes.
          type: object
        reference_id:
          description: >-
            Correlation ID for hold/release correlation and auth/settle
            reconciliation (LOT mode only).

            Static literal (e.g. "auth_12345") or ${{ }} CEL expression (e.g.
            "${{ event.authorization_id }}").
          example: ${{ event.authorization_id }}
          type: string
        reset_after:
          description: >-
            Auto-reset duration for COUNTER actions. The counter resets to 0
            after this duration elapses since the last reset; h/d/w units (e.g.
            "30d", "720h"). Send an empty string to remove auto-reset.
          example: 30d
          type: string
        tag:
          description: Tag name to add (TAG) or remove (UNTAG)
          example: VIP
          type: string
        target:
          allOf:
            - $ref: '#/components/schemas/models.ActionTarget'
          description: >-
            Alternate recipient for this action. Defaults to the event's
            participant. Supported for CREDIT, DEBIT, TAG, UNTAG, COUNTER,
            SET_ATTRIBUTE, SET_TIER.
        tier:
          description: Tier track name (e.g. "status", "loyalty")
          example: status
          type: string
        type:
          description: >-
            Action kind. Valid values: CREDIT, DEBIT, HOLD, RELEASE, FORFEIT,
            VOID_HOLD, TAG, UNTAG, COUNTER, SET_ATTRIBUTE, SET_TIER,
            SCHEDULE_EVENT, BROADCAST
          example: CREDIT
          type: string
        value:
          description: >-
            COUNTER: static numeric string or ${{ }} CEL expression.
            SET_ATTRIBUTE: literal string or ${{ }} CEL expression.
          example: '1'
          type: string
      type: object
    models.BudgetSpec:
      properties:
        asset_id:
          description: Asset this budget constrains
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        cron_expression:
          description: >-
            Cron expression for CRON-based resets (e.g. "0 0 1 * *" for first of
            every month)
          example: 0 0 1 * *
          type: string
        interval:
          description: Duration for INTERVAL-based resets; h/d/w units (e.g. "30d", "720h")
          example: 30d
          type: string
        limit:
          description: Maximum allowed spend per budget period (decimal string)
          example: '10000.00'
          type: string
        schedule_type:
          description: >-
            Reset schedule type: CRON or INTERVAL. Omit for lifetime (no-reset)
            budgets.
          example: CRON
          type: string
      type: object
    handlers.BudgetResponse:
      properties:
        asset_id:
          description: Asset this budget constrains
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        consumed:
          description: Amount consumed so far in the current period
          example: '4500.00'
          type: string
        cron_expression:
          description: Cron expression for CRON-based resets (e.g., first of every month)
          example: 0 0 1 * *
          type: string
        interval:
          description: Duration for INTERVAL-based resets
          example: 720h
          type: string
        limit:
          description: Maximum allowed spend for this budget period
          example: '10000.00'
          type: string
        next_reset_at:
          description: When the budget next resets (null for lifetime budgets)
          example: '2026-03-01T00:00:00Z'
          format: date-time
          type: string
        schedule_type:
          description: 'Reset schedule type: CRON or INTERVAL (null for lifetime budgets)'
          example: CRON
          type: string
      type: object
    models.RuleWarning:
      properties:
        code:
          description: >-
            Machine-readable warning category, e.g.
            RuleWarningCodeUnknownStateKey.
          example: unknown_state_key
          type: string
        key:
          description: The referenced key, as written in the condition.
          example: purchaseCnt
          type: string
        kind:
          description: >-
            State kind: StateKindCounter, StateKindTag, StateKindAttribute, or
            StateKindAlias.
          example: counter
          type: string
        message:
          description: >-
            Human-readable explanation (includes the suggestion when one
            exists).
          example: >-
            unknown counter "purchaseCnt" — no rule in this program writes it;
            did you mean "purchase_count"?
          type: string
        scope:
          description: >-
            State scope of the reference: StateScopeParticipant,
            StateScopeProgram, or StateScopeGroup.
          example: participant
          type: string
        suggestion:
          description: Closest known key, when a near-match exists. Empty otherwise.
          example: purchase_count
          type: string
      type: object
    handlers.ErrorDetails:
      description: >-
        Optional structured details about the error. Always a JSON object: a
        `fields` array for validation errors, or flat
        field/reason/expected/received properties for other input errors.
      properties:
        expected:
          description: Expected value or format (non-validation input errors)
          example: uuid
          type: string
        field:
          description: Field name that caused the error (non-validation input errors)
          example: asset_id
          type: string
        fields:
          description: >-
            Fields lists each offending input field on validation_error
            responses.
          items:
            $ref: '#/components/schemas/handlers.ErrorFieldDetail'
          type: array
        reason:
          description: Machine-readable reason code (non-validation input errors)
          example: invalid
          type: string
        received:
          description: Value that was received (non-validation input errors)
          example: not-a-uuid
          type: string
      type: object
    models.ActionTarget:
      properties:
        external_id:
          description: >-
            ExternalID is a ${{ }} CEL expression that evaluates to an
            external_id string.

            Cannot be combined with ID or ParticipantID.

            Example: "${{ event.referrer_id }}" where referrer_id contains an
            external_id like "user_123"
          example: ${{ event.referrer_id }}
          type: string
        id:
          description: >-
            ID is the public entity ID: participant_id for PARTICIPANT, group_id
            for GROUP.
          example: 550e8400-e29b-41d4-a716-446655440000
          format: uuid
          type: string
        participant_id:
          description: >-
            ParticipantID is a ${{ }} CEL expression that evaluates to a
            participant UUID string.

            Cannot be combined with ID or ExternalID.

            Example: "${{ event.recipient_id }}" where recipient_id contains a
            participant UUID
          example: ${{ event.recipient_id }}
          type: string
        type:
          description: >-
            Type is the entity type: PARTICIPANT, GROUP, or PROGRAM.

            Optional when using id or dynamic ID fields (defaults to
            PARTICIPANT).
          example: PARTICIPANT
          type: string
      type: object
    handlers.ErrorFieldDetail:
      description: >-
        A single field-level error: which input field failed, why, and (where
        known) the expected and received values.
      properties:
        expected:
          description: >-
            Expected value or format. Usually a string; for "one of" constraints
            it

            is an array of the allowed values.
        field:
          description: Field name that caused the error
          example: amount
          type: string
        message:
          description: Human-readable explanation (validation errors only)
          example: This field is required
          type: string
        reason:
          description: Machine-readable reason code
          example: required
          type: string
        received:
          description: Value that was received
          example: '-10.00'
          type: string
      type: object
  securitySchemes:
    ApiKeyAuth:
      description: API key passed in the X-API-Key header.
      in: header
      name: X-API-Key
      type: apiKey
    BearerAuth:
      description: Bearer token passed in the Authorization header (e.g. "Bearer sk_...").
      in: header
      name: Authorization
      type: apiKey

````