Skip to main content
POST
/
v1
/
rules
Create a rule
curl --request POST \
  --url https://api.scrip.dev/v1/rules \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --data @- <<EOF
{
  "actions": [
    {
      "allow_negative": false,
      "amount": "${{ event.amount * 0.03 }}",
      "asset_id": "550e8400-e29b-41d4-a716-446655440000",
      "bucket": "AVAILABLE",
      "delay": "24h",
      "description": "Purchase reward",
      "event_name": "check_status",
      "expires_at": "365d",
      "key": "purchase_count",
      "level": "gold",
      "matures_at": "30d",
      "payload": {},
      "reference_id": "${{ event.authorization_id }}",
      "reset_after": "30d",
      "tag": "VIP",
      "target": {
        "external_id": "${{ event.referrer_id }}",
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "participant_id": "${{ event.recipient_id }}",
        "type": "PARTICIPANT"
      },
      "tier": "status",
      "type": "CREDIT",
      "value": "1"
    }
  ],
  "condition": "event.type == 'purchase'",
  "name": "Purchase Reward",
  "program_id": "550e8400-e29b-41d4-a716-446655440000",
  "active_from": "2024-01-01T00:00:00Z",
  "active_to": "2024-12-31T23:59:59Z",
  "budgets": [
    {
      "asset_id": "550e8400-e29b-41d4-a716-446655440000",
      "cron_expression": "0 0 1 * *",
      "interval": "30d",
      "limit": "10000.00",
      "schedule_type": "CRON"
    }
  ],
  "description": "Awards 10 points per dollar spent",
  "order": 100,
  "status": "ACTIVE",
  "stop_after_match": false
}
EOF
import requests

url = "https://api.scrip.dev/v1/rules"

payload = {
"actions": [
{
"allow_negative": False,
"amount": "${{ event.amount * 0.03 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"bucket": "AVAILABLE",
"delay": "24h",
"description": "Purchase reward",
"event_name": "check_status",
"expires_at": "365d",
"key": "purchase_count",
"level": "gold",
"matures_at": "30d",
"payload": {},
"reference_id": "${{ event.authorization_id }}",
"reset_after": "30d",
"tag": "VIP",
"target": {
"external_id": "${{ event.referrer_id }}",
"id": "550e8400-e29b-41d4-a716-446655440000",
"participant_id": "${{ event.recipient_id }}",
"type": "PARTICIPANT"
},
"tier": "status",
"type": "CREDIT",
"value": "1"
}
],
"condition": "event.type == 'purchase'",
"name": "Purchase Reward",
"program_id": "550e8400-e29b-41d4-a716-446655440000",
"active_from": "2024-01-01T00:00:00Z",
"active_to": "2024-12-31T23:59:59Z",
"budgets": [
{
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"cron_expression": "0 0 1 * *",
"interval": "30d",
"limit": "10000.00",
"schedule_type": "CRON"
}
],
"description": "Awards 10 points per dollar spent",
"order": 100,
"status": "ACTIVE",
"stop_after_match": False
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
actions: [
{
allow_negative: false,
amount: '${{ event.amount * 0.03 }}',
asset_id: '550e8400-e29b-41d4-a716-446655440000',
bucket: 'AVAILABLE',
delay: '24h',
description: 'Purchase reward',
event_name: 'check_status',
expires_at: '365d',
key: 'purchase_count',
level: 'gold',
matures_at: '30d',
payload: {},
reference_id: '${{ event.authorization_id }}',
reset_after: '30d',
tag: 'VIP',
target: {
external_id: '${{ event.referrer_id }}',
id: '550e8400-e29b-41d4-a716-446655440000',
participant_id: '${{ event.recipient_id }}',
type: 'PARTICIPANT'
},
tier: 'status',
type: 'CREDIT',
value: '1'
}
],
condition: 'event.type == \'purchase\'',
name: 'Purchase Reward',
program_id: '550e8400-e29b-41d4-a716-446655440000',
active_from: '2024-01-01T00:00:00Z',
active_to: '2024-12-31T23:59:59Z',
budgets: [
{
asset_id: '550e8400-e29b-41d4-a716-446655440000',
cron_expression: '0 0 1 * *',
interval: '30d',
limit: '10000.00',
schedule_type: 'CRON'
}
],
description: 'Awards 10 points per dollar spent',
order: 100,
status: 'ACTIVE',
stop_after_match: false
})
};

fetch('https://api.scrip.dev/v1/rules', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scrip.dev/v1/rules",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'actions' => [
[
'allow_negative' => false,
'amount' => '${{ event.amount * 0.03 }}',
'asset_id' => '550e8400-e29b-41d4-a716-446655440000',
'bucket' => 'AVAILABLE',
'delay' => '24h',
'description' => 'Purchase reward',
'event_name' => 'check_status',
'expires_at' => '365d',
'key' => 'purchase_count',
'level' => 'gold',
'matures_at' => '30d',
'payload' => [

],
'reference_id' => '${{ event.authorization_id }}',
'reset_after' => '30d',
'tag' => 'VIP',
'target' => [
'external_id' => '${{ event.referrer_id }}',
'id' => '550e8400-e29b-41d4-a716-446655440000',
'participant_id' => '${{ event.recipient_id }}',
'type' => 'PARTICIPANT'
],
'tier' => 'status',
'type' => 'CREDIT',
'value' => '1'
]
],
'condition' => 'event.type == \'purchase\'',
'name' => 'Purchase Reward',
'program_id' => '550e8400-e29b-41d4-a716-446655440000',
'active_from' => '2024-01-01T00:00:00Z',
'active_to' => '2024-12-31T23:59:59Z',
'budgets' => [
[
'asset_id' => '550e8400-e29b-41d4-a716-446655440000',
'cron_expression' => '0 0 1 * *',
'interval' => '30d',
'limit' => '10000.00',
'schedule_type' => 'CRON'
]
],
'description' => 'Awards 10 points per dollar spent',
'order' => 100,
'status' => 'ACTIVE',
'stop_after_match' => false
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "https://api.scrip.dev/v1/rules"

payload := strings.NewReader("{\n \"actions\": [\n {\n \"allow_negative\": false,\n \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"bucket\": \"AVAILABLE\",\n \"delay\": \"24h\",\n \"description\": \"Purchase reward\",\n \"event_name\": \"check_status\",\n \"expires_at\": \"365d\",\n \"key\": \"purchase_count\",\n \"level\": \"gold\",\n \"matures_at\": \"30d\",\n \"payload\": {},\n \"reference_id\": \"${{ event.authorization_id }}\",\n \"reset_after\": \"30d\",\n \"tag\": \"VIP\",\n \"target\": {\n \"external_id\": \"${{ event.referrer_id }}\",\n \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_id\": \"${{ event.recipient_id }}\",\n \"type\": \"PARTICIPANT\"\n },\n \"tier\": \"status\",\n \"type\": \"CREDIT\",\n \"value\": \"1\"\n }\n ],\n \"condition\": \"event.type == 'purchase'\",\n \"name\": \"Purchase Reward\",\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"active_from\": \"2024-01-01T00:00:00Z\",\n \"active_to\": \"2024-12-31T23:59:59Z\",\n \"budgets\": [\n {\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"cron_expression\": \"0 0 1 * *\",\n \"interval\": \"30d\",\n \"limit\": \"10000.00\",\n \"schedule_type\": \"CRON\"\n }\n ],\n \"description\": \"Awards 10 points per dollar spent\",\n \"order\": 100,\n \"status\": \"ACTIVE\",\n \"stop_after_match\": false\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.scrip.dev/v1/rules")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"actions\": [\n {\n \"allow_negative\": false,\n \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"bucket\": \"AVAILABLE\",\n \"delay\": \"24h\",\n \"description\": \"Purchase reward\",\n \"event_name\": \"check_status\",\n \"expires_at\": \"365d\",\n \"key\": \"purchase_count\",\n \"level\": \"gold\",\n \"matures_at\": \"30d\",\n \"payload\": {},\n \"reference_id\": \"${{ event.authorization_id }}\",\n \"reset_after\": \"30d\",\n \"tag\": \"VIP\",\n \"target\": {\n \"external_id\": \"${{ event.referrer_id }}\",\n \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_id\": \"${{ event.recipient_id }}\",\n \"type\": \"PARTICIPANT\"\n },\n \"tier\": \"status\",\n \"type\": \"CREDIT\",\n \"value\": \"1\"\n }\n ],\n \"condition\": \"event.type == 'purchase'\",\n \"name\": \"Purchase Reward\",\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"active_from\": \"2024-01-01T00:00:00Z\",\n \"active_to\": \"2024-12-31T23:59:59Z\",\n \"budgets\": [\n {\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"cron_expression\": \"0 0 1 * *\",\n \"interval\": \"30d\",\n \"limit\": \"10000.00\",\n \"schedule_type\": \"CRON\"\n }\n ],\n \"description\": \"Awards 10 points per dollar spent\",\n \"order\": 100,\n \"status\": \"ACTIVE\",\n \"stop_after_match\": false\n}")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.scrip.dev/v1/rules")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"actions\": [\n {\n \"allow_negative\": false,\n \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"bucket\": \"AVAILABLE\",\n \"delay\": \"24h\",\n \"description\": \"Purchase reward\",\n \"event_name\": \"check_status\",\n \"expires_at\": \"365d\",\n \"key\": \"purchase_count\",\n \"level\": \"gold\",\n \"matures_at\": \"30d\",\n \"payload\": {},\n \"reference_id\": \"${{ event.authorization_id }}\",\n \"reset_after\": \"30d\",\n \"tag\": \"VIP\",\n \"target\": {\n \"external_id\": \"${{ event.referrer_id }}\",\n \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_id\": \"${{ event.recipient_id }}\",\n \"type\": \"PARTICIPANT\"\n },\n \"tier\": \"status\",\n \"type\": \"CREDIT\",\n \"value\": \"1\"\n }\n ],\n \"condition\": \"event.type == 'purchase'\",\n \"name\": \"Purchase Reward\",\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"active_from\": \"2024-01-01T00:00:00Z\",\n \"active_to\": \"2024-12-31T23:59:59Z\",\n \"budgets\": [\n {\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"cron_expression\": \"0 0 1 * *\",\n \"interval\": \"30d\",\n \"limit\": \"10000.00\",\n \"schedule_type\": \"CRON\"\n }\n ],\n \"description\": \"Awards 10 points per dollar spent\",\n \"order\": 100,\n \"status\": \"ACTIVE\",\n \"stop_after_match\": false\n}"

response = http.request(request)
puts response.read_body
{
  "actions": [
    {
      "amount": "${{ event.amount * 10 }}",
      "asset_id": "550e8400-e29b-41d4-a716-446655440002",
      "type": "CREDIT"
    }
  ],
  "active_from": "2024-01-01T00:00:00Z",
  "active_to": "2024-12-31T23:59:59Z",
  "budgets": [
    {
      "asset_id": "550e8400-e29b-41d4-a716-446655440000",
      "consumed": "4500.00",
      "cron_expression": "0 0 1 * *",
      "interval": "720h",
      "limit": "10000.00",
      "next_reset_at": "2026-03-01T00:00:00Z",
      "schedule_type": "CRON"
    }
  ],
  "condition": "event.type == 'purchase' && event.amount > 0",
  "created_at": "2024-01-15T10:30:00Z",
  "deleted_at": "2024-06-01T00:00:00Z",
  "description": "Awards 10 points per dollar spent",
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Purchase Reward",
  "order": 100,
  "program_id": "550e8400-e29b-41d4-a716-446655440001",
  "status": "ACTIVE",
  "stop_after_match": false,
  "updated_at": "2024-01-15T10:30:00Z",
  "warnings": [
    {
      "code": "unknown_state_key",
      "key": "purchaseCnt",
      "kind": "counter",
      "message": "unknown counter \"purchaseCnt\" — no rule in this program writes it; did you mean \"purchase_count\"?",
      "scope": "participant",
      "suggestion": "purchase_count"
    }
  ]
}
{
"code": "bad_request",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Invalid request parameters"
}
{
"code": "unauthorized",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Missing or invalid credentials"
}
{
"code": "forbidden",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Insufficient permissions for this action"
}
{
"code": "not_found",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Resource not found"
}
{
"code": "conflict",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Resource already exists or state conflict"
}
{
"code": "unsupported_media_type",
"details": {
"expected": "uuid",
"field": "asset_id",
"fields": [
{
"expected": "<unknown>",
"field": "amount",
"message": "This field is required",
"reason": "required",
"received": "-10.00"
}
],
"reason": "invalid",
"received": "not-a-uuid"
},
"message": "Content-Type must be application/json"
}
{
"code": "internal_error",
"message": "An internal error occurred"
}
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. 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.
For usage patterns and examples, see the Writing Rules guide.

Authorizations

X-API-Key
string
header
required

API key passed in the X-API-Key header.

Body

application/json

Rule with CEL condition and actions array

Request body for creating a new rule with CEL condition and actions

actions
object[]
required

Actions to execute when the condition matches (must be non-empty). Each action has a type field that determines which other fields are relevant.

Minimum array length: 1
condition
string
required

CEL expression that determines when the rule fires

Minimum string length: 1
Example:

"event.type == 'purchase'"

name
string
required

Display name for the rule

Required string length: 1 - 255
Example:

"Purchase Reward"

program_id
string<uuid>
required

Program to attach this rule to

Example:

"550e8400-e29b-41d4-a716-446655440000"

active_from
string<date-time>

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"

active_to
string<date-time>

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"

budgets
object[]

Optional budget constraints for this rule. Omit or pass null for no budgets.

description
string

Human-readable summary of what this rule does

Maximum string length: 1000
Example:

"Awards 10 points per dollar spent"

order
integer

Evaluation sequence (lower = first). Auto-assigned if omitted.

Required range: x >= 1
Example:

100

status
enum<string>

Initial lifecycle status (ACTIVE or SUSPENDED). Defaults to ACTIVE.

Available options:
ACTIVE,
SUSPENDED
Example:

"ACTIVE"

stop_after_match
boolean

When true, no subsequent rules evaluate after this one matches

Example:

false

Response

Rule created

actions
object[]

Actions to execute when the condition matches

Example:
[
{
"amount": "${{ event.amount * 10 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440002",
"type": "CREDIT"
}
]
active_from
string<date-time>

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"

active_to
string<date-time>

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"

budgets
object[]

Budget constraints applied to this rule

condition
string

CEL expression that determines when the rule fires

Example:

"event.type == 'purchase' && event.amount > 0"

created_at
string<date-time>

When this rule was created (RFC 3339)

Example:

"2024-01-15T10:30:00Z"

deleted_at
string<date-time>

When this rule was archived (null if not archived)

Example:

"2024-06-01T00:00:00Z"

description
string

Human-readable summary of what this rule does

Example:

"Awards 10 points per dollar spent"

id
string<uuid>

Unique identifier for this rule

Example:

"550e8400-e29b-41d4-a716-446655440000"

name
string

Display name

Example:

"Purchase Reward"

order
integer

Evaluation sequence (lower = first, must be unique per program)

Example:

100

program_id
string<uuid>

Program this rule belongs to

Example:

"550e8400-e29b-41d4-a716-446655440001"

status
string

Lifecycle status: ACTIVE, SUSPENDED, or ARCHIVED

Example:

"ACTIVE"

stop_after_match
boolean

When true, no subsequent rules evaluate after this one matches

Example:

false

updated_at
string<date-time>

When this rule was last modified (RFC 3339)

Example:

"2024-01-15T10:30:00Z"

warnings
object[]

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.