Core conceptsRules

Rules

The single place enforcement is decided — a safe, composable boolean grammar over every signal field, compiled to an AST and never eval'd.

Rules are the only place Botect decides to block or challenge. A rule is a boolean expression over signal fields plus an action to take when it matches. They cover everything from the simple case (act on a score band) to a decision that depends on a specific path, country, detection ID, or behavioral threshold.

In the resolution order rules run after the two guards (allow_verified, protect_static) and are the last word: the first active rule that matches and carries a terminating action wins, and its name appears in the verdict reason.

Starter rules

Every project is provisioned with two rules covering the enforceable bands. They ship inactive — Botect never starts blocking real traffic on your behalf — so turning one on is the normal way to begin enforcing.

NameExpressionAction
Block confirmed automationband == "definite"block
Challenge likely-automated trafficband == "likely_automated"challenge

Activating a rule also feeds edge enforcement: a rule whose expression reads only band can be honored by an IP list, so its matching IPs are synced to Cloudflare. A rule that reads path, country, ua or score cannot — an IP list is request-blind — so it applies at the verdict API only.

Safety

Rule expressions are a constrained grammar, not code. At save time each expression is parsed and validated into an abstract syntax tree (AST); the verdict path walks the stored AST. There is no eval, no method calls, no code execution — an invalid or out-of-grammar expression is rejected with a 422 when you create the rule, never at evaluation time.

Grammar

expr      := orExpr
orExpr    := andExpr ( "OR" andExpr )*
andExpr   := unary  ( "AND" unary )*
unary     := "NOT" unary | "(" expr ")" | predicate
predicate := comparison | boolField
comparison:= field op value
boolField := verified_bot | js_detection.passed | static_resource   (bare truthiness)
op        := "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "not in"
value     := number | "quoted string" | "[" list "]" | true | false | null

Allow-listed fields

score, band, verified_bot, verified_bot_category, js_detection.passed, static_resource, detection_ids, path, ip, country, ua, behavioral.mouse_entropy, behavioral.scroll_velocity, behavioral.visibility_changes, behavioral.first_input_delay_ms.

Any field outside this list is rejected. See Signals & fields for each field's meaning and type.

Operators by type

Field typeFieldsOperators
Numericscore, behavioral.*== != < <= > >=
Booleanverified_bot, js_detection.passed, static_resource== != (or bare)
Stringpath, country, ua, verified_bot_category, band== != in not in
Arraydetection_idsin / not in (membership)

Examples

score < 30 AND path == "/login" AND NOT verified_bot
→ block

detection_ids in [50331648, 50331651] AND NOT verified_bot
→ block

band == "likely_automated" AND country in ["RU", "CN"]
→ challenge

behavioral.mouse_entropy < 0.2 AND behavioral.visibility_changes == 0
→ challenge

Creating a rule

Send the expression text and an action. Botect compiles and validates it before storing.

curl -X POST https://api.botect.ai/v1/projects/123/rules \
  -H "Authorization: Bearer YOUR_ACCOUNT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Protect login from bots",
    "expression": "score < 30 AND path == "/login" AND NOT verified_bot",
    "action": "block",
    "sort_order": 10
  }'

See Create a rule for the full request and response. Rules are listed in sort_order (ascending) and evaluated in that order.

Order decides precedence. A narrow exemption only overrides a broad rule if it sits above it — give your allow rules a lower sort_order than the band rules they are meant to pre-empt. The starter rules sit at 10 and 20.

Always guard enforcement rules with AND NOT verified_bot unless you specifically intend to act on verified crawlers — otherwise a misconfigured rule could block Google or an AI crawler you want indexing your site.

Evaluation

When a verdict is read, active rules run in sort_order. Every action terminates evaluation with that action as the verdict — allow and delay included — except log, which records the match and lets evaluation continue so a later block or challenge still applies. If no rule matches, the verdict is allow.

The matched rule populates the verdict reason and is recorded on the block itself, so you can always trace why a request was acted on.

Rule changes apply to verdicts computed from then on. A verdict already cached for a session is served until it expires, so allow up to the verdict cache TTL (60s by default) for a change to be visible everywhere.