Skip to content

Rule object

Rules live in a field's on array. They run in code right after the field's stage finishes. See Flow rules for the concepts; this page lists every key, action and operator.

{"when": {"gte": 3}, "do": "reject"}
Key Type Description
when object or array Conditions (all must match). Omitted = always. An array is shorthand for {"in": [...]}.
do string The action, or a field name to jump to. Required.
to string Target field, with "do": "jump".
cases, default object, string Branches, with "do": "switch".
max integer Times a return or jump may fire (default 3, minimum 1).
at string Path inside the value to test (e.g. "status", "data.items[0].id"), for object values.
tol number Tolerance for *_expr operators.

Order of evaluation

  1. After a stage, fields are checked in schema order; each field's rules in array order.
  2. A rule whose when matches is applied.
  3. Markers (review, drop) apply and checking continues with the next rule.
  4. The first flow action (reject, return, jump, switch, end) applies and stops checking — for that field and for the whole stage.

Actions

reject

Stop the request.

{"when": {"len_lt": 16}, "do": "reject"}
  • No further stage runs.
  • Fields of the same stage and fields that did not run get their on_reject (default null); fields from earlier stages keep their values.
  • Response: rejected: {"field": "…", "value": …}.

review

Flag the field for a human; continue.

{"when": {"confidence_lt": 0.9}, "do": "review"}
  • The field is added to flagged (with a readable reason) and to needs_review.
  • Several matching review rules on one field give one flag with the reasons joined.

drop

Keep the value in result, hide it from later fields' prompts.

{"do": "drop"}

return

Start over: every field runs again and sees the previous round's values and the reason (for *_expr rules, the expected value; for confidence rules, a note that the answer was uncertain).

{"when": {"ne_expr": "subtotal + ppn", "tol": 1}, "do": "return", "max": 1}

Limited by max (default 3); after that the rule is skipped and later rules apply.

Jump

Go to another field. Write the field name, or {"do": "jump", "to": "…"}.

{"when": ["wrong"], "do": "total", "max": 2}
  • Forward: fields between here and the target that have not run are skipped (null); the run continues from the target.
  • Backward (or to itself): the target and every field after it (schema order) run again, seeing their previous values.
  • Limited by max.

switch

Run one of several fields, chosen by this field's value.

{"do": "switch",
 "cases": {"infra": "msg_infra", "dev": "msg_dev"},
 "default": "msg_other"}
  • Values are compared as text: true/false, 3 for 3.0, null.
  • The chosen field runs normally (by its depends_on); the other targets are skipped (null).
  • No matching case and no default → all targets skipped.

end

Finish successfully now. Fields that did not run are null (not on_reject), rejected stays null, and ended is set.

{"when": {"eq": "not_applicable"}, "do": "end"}

Condition operators

All operators in one when must match (AND). For OR, write two rules.

Equality and membership (any type)

Operator Example Matches Does not match
eq {"eq": "active"} "active", " ACTIVE " "inactive"
ne {"ne": "active"} "lapsed" "Active"
in {"in": ["low", "medium"]} "low" "high"
not_in {"not_in": ["low"]} "high" "LOW"

Text is compared trimmed and case-insensitive. Booleans never equal numbers (true is not 1). {"eq": null} matches null.

Numeric comparison

Operator Meaning Example Matches Does not match
lt less than {"lt": 21} 20 21, 30
lte less than or equal {"lte": 40} 40, 12.5 40.01
gt greater than {"gt": 55} 56 55
gte greater than or equal {"gte": 3} 3, 5 2

A range is two operators in one when: {"gte": 21, "lte": 55} matches 21–55 inclusive. Non-numbers (text, null, booleans) never match.

Length

Operator Meaning Example Matches Does not match
len_eq length equals {"len_eq": 16} "3201456789012345" "320145678901234"
len_lt shorter than {"len_lt": 16} 15 characters 16 characters
len_lte, len_gt, len_gte ≤, >, ≥ {"len_gt": 0} ["a"] []

Length of trimmed text, or number of list items. Other types never match.

Text and lists

Operator Example Matches Does not match
contains {"contains": "cash"} "Paid in CASH" "QRIS"
contains (list) {"contains": 500} [404, 500] [404]
match {"match": "\\d{16}"} "3201456789012345" "3201-4567", "32014567890123456"
not_match {"not_match": "\\d{16}"} "3201-4567" "3201456789012345"

match and not_match use regular expressions and must match the whole trimmed text. Remember to escape backslashes in JSON ("\\d"). To search anywhere in the text, wrap the pattern: "(?is).*(approved|released).*".

Cross-field expressions

Compare this field's number with a formula over other fields.

Operator Matches when Example
eq_expr value = formula (within tol) {"eq_expr": "a + b"}
ne_expr value ≠ formula {"ne_expr": "subtotal - discount + vat", "tol": 1}
lt_expr, lte_expr value < / ≤ formula {"lt_expr": "api_price * 0.95"}
gt_expr, gte_expr value > / ≥ formula {"gt_expr": "api_price * 1.05"}
  • Formula syntax as in compute.
  • tol: absolute difference still treated as equal (default: exact, allowing for rounding noise). lte_expr/gte_expr also treat "within tol" as equal.
  • Referenced fields must run in the same or an earlier stage (else HTTP 400). They do not need to be dependencies.
  • If this value or any referenced value is null or not a number, or the formula divides by zero, the condition does not match.

Example: 108.000 with {"ne_expr": "a + b", "tol": 1} and a + b = 108.900 → matches (they differ by 900). 108.900,4 → does not match.

Confidence

Compare the field's confidence (0–1).

Operator Example Matches (confidence)
confidence_lt {"confidence_lt": 0.9} 0.77
confidence_lte {"confidence_lte": 0.9} 0.9
confidence_gt {"confidence_gt": 0.99} 0.9998
confidence_gte {"confidence_gte": 0.99} 0.99

The argument must be a number between 0 and 1. Fields without a confidence (free_form, compute, call) never match; using these operators on a free_form field is rejected with HTTP 400.

Combining with at

For fields whose value is an object (usually call), at selects the part to test:

"customer_data": {"type": "call", "http": {"url": "https://crm.example/policy/{n}"},
                  "args": {"n": "$policy_number"},
                  "on": [{"when": {"eq": null}, "do": "reject"},
                         {"at": "policy_status", "when": {"ne": "active"}, "do": "reject"}]}