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": [
{
"amount": "${{ event.amount * 0.03 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"description": "Purchase reward",
"expires_at": "365d",
"matures_at": "30d",
"type": "CREDIT"
}
],
"program_id": "550e8400-e29b-41d4-a716-446655440001",
"rule_id": "550e8400-e29b-41d4-a716-446655440002"
}
EOFimport requests
url = "https://api.scrip.dev/v1/rules/validate"
payload = {
"condition": "event.type == 'purchase' && event.amount > 100",
"actions": [
{
"amount": "${{ event.amount * 0.03 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"description": "Purchase reward",
"expires_at": "365d",
"matures_at": "30d",
"type": "CREDIT"
}
],
"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: [
{
amount: '${{ event.amount * 0.03 }}',
asset_id: '550e8400-e29b-41d4-a716-446655440000',
description: 'Purchase reward',
expires_at: '365d',
matures_at: '30d',
type: 'CREDIT'
}
],
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' => [
[
'amount' => '${{ event.amount * 0.03 }}',
'asset_id' => '550e8400-e29b-41d4-a716-446655440000',
'description' => 'Purchase reward',
'expires_at' => '365d',
'matures_at' => '30d',
'type' => 'CREDIT'
]
],
'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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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": [
{
"client_ref": "<string>",
"code": "unknown_state_key",
"key": "purchaseCnt",
"kind": "counter",
"message": "unknown counter \"purchaseCnt\" — no rule in this program writes it; did you mean \"purchase_count\"?",
"rule_id": "<string>",
"rule_set_id": "<string>",
"scope": "participant",
"suggestion": "purchase_count"
}
]
}{
"code": "bad_request",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Invalid request parameters"
}{
"code": "unauthorized",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Missing or invalid credentials"
}{
"code": "forbidden",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Insufficient permissions for this action"
}{
"code": "unsupported_media_type",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Content-Type must be application/json"
}{
"code": "internal_error",
"message": "An internal error occurred"
}Validate a CEL condition
Check whether a CEL expression is syntactically valid without creating a rule.
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": [
{
"amount": "${{ event.amount * 0.03 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"description": "Purchase reward",
"expires_at": "365d",
"matures_at": "30d",
"type": "CREDIT"
}
],
"program_id": "550e8400-e29b-41d4-a716-446655440001",
"rule_id": "550e8400-e29b-41d4-a716-446655440002"
}
EOFimport requests
url = "https://api.scrip.dev/v1/rules/validate"
payload = {
"condition": "event.type == 'purchase' && event.amount > 100",
"actions": [
{
"amount": "${{ event.amount * 0.03 }}",
"asset_id": "550e8400-e29b-41d4-a716-446655440000",
"description": "Purchase reward",
"expires_at": "365d",
"matures_at": "30d",
"type": "CREDIT"
}
],
"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: [
{
amount: '${{ event.amount * 0.03 }}',
asset_id: '550e8400-e29b-41d4-a716-446655440000',
description: 'Purchase reward',
expires_at: '365d',
matures_at: '30d',
type: 'CREDIT'
}
],
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' => [
[
'amount' => '${{ event.amount * 0.03 }}',
'asset_id' => '550e8400-e29b-41d4-a716-446655440000',
'description' => 'Purchase reward',
'expires_at' => '365d',
'matures_at' => '30d',
'type' => 'CREDIT'
]
],
'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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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 \"amount\": \"${{ event.amount * 0.03 }}\",\n \"asset_id\": \"550e8400-e29b-41d4-a716-446655440000\",\n \"description\": \"Purchase reward\",\n \"expires_at\": \"365d\",\n \"matures_at\": \"30d\",\n \"type\": \"CREDIT\"\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": [
{
"client_ref": "<string>",
"code": "unknown_state_key",
"key": "purchaseCnt",
"kind": "counter",
"message": "unknown counter \"purchaseCnt\" — no rule in this program writes it; did you mean \"purchase_count\"?",
"rule_id": "<string>",
"rule_set_id": "<string>",
"scope": "participant",
"suggestion": "purchase_count"
}
]
}{
"code": "bad_request",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Invalid request parameters"
}{
"code": "unauthorized",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Missing or invalid credentials"
}{
"code": "forbidden",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Insufficient permissions for this action"
}{
"code": "unsupported_media_type",
"details": {
"expected": "<string>",
"field": "<string>",
"fields": [
{
"expected": "<unknown>",
"field": "<string>",
"message": "This field is required",
"reason": "required",
"received": "<string>"
}
],
"reason": "invalid",
"received": "<string>"
},
"message": "Content-Type must be application/json"
}{
"code": "internal_error",
"message": "An internal error occurred"
}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.
Authorizations
API key passed in the X-API-Key header.
Body
Condition to validate
CEL expression to validate
1"event.type == 'purchase' && event.amount > 100"
Draft actions to check along with the condition. Incomplete actions are accepted. Expressions are checked as they are when saving a rule. With program_id, validation also checks asset restrictions and recognizes keys these actions set, so reading those keys does not produce an unknown-key warning.
Show child attributes
Show child attributes
Program to check the draft against. Checks tier keys and warns about counters, tags, or attributes that no rule in the program sets. With actions, also checks whether the asset supports expires_at, matures_at, and reference_id, plus reference_id length and format. Without program_id, checks cover tier field structure, participant fields, and deprecated names.
"550e8400-e29b-41d4-a716-446655440001"
Rule you are editing. Excludes its saved actions from the known state keys so renaming a key in the draft does not hide a warning. Omit when creating a rule.
"550e8400-e29b-41d4-a716-446655440002"
Response
Validation result
Human-readable validation result or error detail
"CEL condition is valid"
Additional context about validation scope (present only on success)
"Validation checks syntax only. Field references are not verified against an event schema."
Whether the condition is syntactically valid
true
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.
Show child attributes
Show child attributes