Skip to main content
POST
/
v1
/
rules
/
simulate
Simulate a draft rule
curl --request POST \
  --url https://api.scrip.dev/v1/rules/simulate \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --data @- <<EOF
{
  "event": {},
  "program_id": "550e8400-e29b-41d4-a716-446655440001",
  "rule": {
    "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"
  },
  "participant_id": "550e8400-e29b-41d4-a716-446655440000",
  "participant_state": {}
}
EOF
import requests

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

payload = {
"event": {},
"program_id": "550e8400-e29b-41d4-a716-446655440001",
"rule": {
"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"
},
"participant_id": "550e8400-e29b-41d4-a716-446655440000",
"participant_state": {}
}
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({
event: {},
program_id: '550e8400-e29b-41d4-a716-446655440001',
rule: {
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'
},
participant_id: '550e8400-e29b-41d4-a716-446655440000',
participant_state: {}
})
};

fetch('https://api.scrip.dev/v1/rules/simulate', 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/simulate",
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([
'event' => [

],
'program_id' => '550e8400-e29b-41d4-a716-446655440001',
'rule' => [
'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'
],
'participant_id' => '550e8400-e29b-41d4-a716-446655440000',
'participant_state' => [

]
]),
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/simulate"

payload := strings.NewReader("{\n \"event\": {},\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule\": {\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 },\n \"participant_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_state\": {}\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/simulate")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"event\": {},\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule\": {\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 },\n \"participant_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_state\": {}\n}")
.asString();
require 'uri'
require 'net/http'

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

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 \"event\": {},\n \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule\": {\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 },\n \"participant_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"participant_state\": {}\n}"

response = http.request(request)
puts response.read_body
{
  "evaluation": {
    "matched": true,
    "reason": "no such key: amount",
    "results": [
      {
        "action": {
          "amount": "${{ event.amount * 10 }}",
          "asset_id": "550e8400-e29b-41d4-a716-446655440002",
          "type": "CREDIT"
        },
        "result": {
          "amount": "750",
          "asset_symbol": "POINTS",
          "description": "Credit 750 POINTS to participant"
        }
      }
    ],
    "status": "evaluated"
  },
  "rule": {
    "condition": "event.type == 'purchase' && event.amount > 0",
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Purchase Reward",
    "order": 100,
    "stop_after_match": false
  },
  "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": "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"
}
Dry-runs a rule that has not been saved yet. Send program_id, a rule object (name, condition, actions), and the same event and participant-context fields as Simulate a rule. Nothing persists: no rule is created, no balances change, and no events are recorded. This is the test step of the authoring loop: validate the condition, simulate the draft here until the behavior looks right, then create the rule. The response shape, participant-context rules, and warnings match the saved-rule simulation endpoint, and the response also includes any validation warnings for the draft’s expressions.

Authorizations

X-API-Key
string
header
required

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

Body

application/json

Program context, draft rule, sample event, and optional participant state

event
object
required

Event is the sample event data to evaluate against the draft rule. Kept as raw JSON so numeric values can be precision-checked before the float64 decode (numbers above ±2^53 are rejected — SCR-323).

program_id
string<uuid>
required

Program providing the validation and evaluation context (asset linkage, tier keys, participant state lookups)

Example:

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

rule
object
required

Rule is the draft rule to evaluate. It is validated exactly like rule create (CEL syntax, action structure, asset existence and program linkage, tier references) but never persisted.

participant_id
string<uuid>

ParticipantID optionally identifies a real participant whose current state (tags, counters, attributes, tiers) is loaded as the simulation's participant context. Mutually exclusive with participant_state.

Example:

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

participant_state
object

ParticipantState is optional simulated participant state (tags, counters, attributes), matching the semantics of POST /v1/rules/{id}/simulate. Mutually exclusive with participant_id.

Response

Simulation result

evaluation
object

Evaluation contains the simulation results

rule
object

Rule contains metadata about the rule that was simulated

warnings
object[]

Non-blocking advisories about the simulation context — e.g. missing_participant_context when the rule's condition reads participant state but the request supplied neither participant_id nor participant_state, so the condition evaluated against empty defaults.