Skip to main content
POST
/
v1
/
rules
/
validate
Validate a CEL condition
curl --request POST \
  --url https://api.scrip.dev/v1/rules/validate \
  --header 'Content-Type: application/json' \
  --header 'X-API-Key: <api-key>' \
  --data @- <<EOF
{
  "condition": "event.type == 'purchase' && event.amount > 100",
  "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"
    }
  ],
  "program_id": "550e8400-e29b-41d4-a716-446655440001",
  "rule_id": "550e8400-e29b-41d4-a716-446655440002"
}
EOF
import requests

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

payload = {
"condition": "event.type == 'purchase' && event.amount > 100",
"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"
}
],
"program_id": "550e8400-e29b-41d4-a716-446655440001",
"rule_id": "550e8400-e29b-41d4-a716-446655440002"
}
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({
condition: 'event.type == \'purchase\' && event.amount > 100',
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'
}
],
program_id: '550e8400-e29b-41d4-a716-446655440001',
rule_id: '550e8400-e29b-41d4-a716-446655440002'
})
};

fetch('https://api.scrip.dev/v1/rules/validate', 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/validate",
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([
'condition' => 'event.type == \'purchase\' && event.amount > 100',
'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'
]
],
'program_id' => '550e8400-e29b-41d4-a716-446655440001',
'rule_id' => '550e8400-e29b-41d4-a716-446655440002'
]),
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/validate"

payload := strings.NewReader("{\n \"condition\": \"event.type == 'purchase' && event.amount > 100\",\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 \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule_id\": \"550e8400-e29b-41d4-a716-446655440002\"\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/validate")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"condition\": \"event.type == 'purchase' && event.amount > 100\",\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 \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule_id\": \"550e8400-e29b-41d4-a716-446655440002\"\n}")
.asString();
require 'uri'
require 'net/http'

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

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 \"condition\": \"event.type == 'purchase' && event.amount > 100\",\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 \"program_id\": \"550e8400-e29b-41d4-a716-446655440001\",\n \"rule_id\": \"550e8400-e29b-41d4-a716-446655440002\"\n}"

response = http.request(request)
puts response.read_body
{
  "message": "CEL condition is valid",
  "note": "Validation checks syntax only. Field references are not verified against an event schema.",
  "valid": true,
  "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": "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"
}
Checks whether a CEL expression is valid without creating a rule. Pass the condition string in the request body. The response returns a valid boolean and a message with details. Pass an optional program_id to validate references against a specific program: tier keys and asset symbols in the expression are checked against that program’s configuration, and the response includes non-blocking vocabulary warnings. Pass an optional draft actions array to run the same checks on action expressions (template syntax, CEL compilation, references) before creating the rule. Field references to event.* are never validated because events are schemaless. For example, event.amount > 100 passes even if your events never include an amount field. Use the simulate endpoint to test a rule against realistic data.
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

Condition to validate

condition
string
required

CEL expression to validate

Minimum string length: 1
Example:

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

actions
object[]

Optional draft actions. When supplied alongside program_id, the keys these actions write count as known vocabulary, so a condition that reads a key the same rule writes does not warn. Used for accurate live validation before the rule is saved; only the action keys/targets are read (no full validation).

program_id
string<uuid>

Optional program ID. When supplied, the condition is also checked against the program: tier key references (participant.tiers.) are validated, and counter/tag/attribute key references that no rule in the program writes produce non-blocking warnings. Without it, only tier fields, participant roots, and deprecation advisories are checked.

Example:

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

rule_id
string<uuid>

Optional ID of the rule being edited. When validating an edit, pass it so the rule's own persisted actions are excluded from the program vocabulary (the stale copy must not mask a renamed key) — matching the behavior on save. Omit when validating a brand-new rule.

Example:

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

Response

Validation result

message
string

Human-readable validation result or error detail

Example:

"CEL condition is valid"

note
string

Additional context about validation scope (present only on success)

Example:

"Validation checks syntax only. Field references are not verified against an event schema."

valid
boolean

Whether the condition is syntactically valid

Example:

true

warnings
object[]

Non-blocking advisories — e.g. a counter/tag/attribute key referenced in a condition/action expression that no rule in the program writes (likely a typo), or a deprecated CEL alias. Never affects validity.