Why use the rules engine
- Flexibility: Create and modify rules without code deploys
- Performance: Compiled expressions evaluate in under 1ms each
- Type safety: Expression syntax validated at rule creation
- No short-circuit: matching rules are evaluated together, so the audit trail records the rules that triggered, not just the winning category
- Scope-based: Apply rules to specific segments, accounts, or transaction types
- Understand rule engine concepts and evaluation flow
- Create and test expression-based rules
- Manage the rule lifecycle (DRAFT, ACTIVE, INACTIVE, DELETED)
- Apply best practices for rule management
What is the rules engine
The rules engine is the Tracer component responsible for evaluating expressions during transaction validation. It enables fraud analysts and risk managers to configure business logic that executes in real time—without requiring code deployments or engineering support.
How it works
Figure 1. Rules engine evaluation flow
- Load rules fetches all active rules from cache (or database on cache miss)
- Evaluate expressions runs the CEL expression of each rule whose scope matches the transaction
- Collect matches gathers all rules that matched and determines the decision
Evaluation pattern
Rules whose scope matches the transaction are evaluated together. There is no priority ordering and no short-circuit evaluation. This ensures:- Complete audit trail (all matching rules recorded)
- No information loss (analysts can see all triggers)
- Simple logic (no priority conflicts)
- DENY — any matching
DENYrule wins outright. - Limit exceeded — if no DENY rule matched but any applicable limit is exceeded, the decision is DENY (rule precedence applies first; limits come in only when no DENY rule matched).
- REVIEW — if no DENY rule matched and no limit was exceeded, any matching
REVIEWrule wins. - ALLOW — if only
ALLOWrules matched, the decision is ALLOW. - Default — if no rule matched at all, Tracer returns the configured
DEFAULT_DECISION_WHEN_NO_MATCH(ALLOWunless explicitly set toDENYfor fail-closed deployments). OnlyALLOWandDENYare accepted;REVIEWis deliberately not a valid no-match default, and any other value fails the service at boot.
matchedRuleIds in the response contains every rule that matched, regardless of the winning category, so audit consumers can see all triggers.
Why DENY beats REVIEW beats ALLOW. Precedence is fixed and not configurable, on purpose: it removes the “which DENY rule wins?” ambiguity at runtime and makes audit trivial — the response always identifies the strictest action that fired. The cost is that you can’t write “ALLOW rules that override DENYs”; if you need that pattern, the right answer is to make the DENY rule more specific instead.
Tracer returns decisions; it does not block transactions directly. Your system receives the decision and is responsible for taking the appropriate action (e.g., blocking, allowing, or queuing for review).
Core concepts
Before creating rules, understand the foundational elements.
Rules
A rule is a unit of business logic composed of:- Expression - A type-safe expression that evaluates to true or false
- Action - What decision to return when the expression is true
- Scopes - Which transactions the rule applies to
- Status - The rule’s lifecycle state
Expressions
Expressions are written in CEL (Common Expression Language), a type-safe language that evaluates transaction context and returns a boolean value (true or false). CEL provides compile-time validation, so syntax errors are caught when you create the rule—not when transactions are being processed. Example expressions:merchant.category is the 4-digit ISO 18245 MCC code — "7995" is the MCC for betting/casino. Both merchant.category and merchant["category"] are accepted; the production examples use bracket notation by convention. If you need to match on a string label like "gambling", store it in metadata and match on that instead.)
Expressions read the validation request through ten variables. For the field types and formats behind each one, see the ValidationRequest schema in the API reference.
Field values worth knowing before you write a condition:
account.statusacceptsactive,suspended,closed, andaccount.typeacceptschecking,savings,credit.merchant.categorytakes a 4-digit ISO 18245 MCC code;merchant.countrytakes an ISO 3166-1 alpha-2 code.- Those four fields are optional on the request. A field the request omits reaches your expression as an empty string, so a condition that tests it for a specific value is false.
segment.segmentId,portfolio.portfolioId,account.accountId, andmerchant.merchantIdare UUID strings.
segmentId and portfolioId live on the top-level segment and portfolio variables, not on account. To match by segment, write segment.segmentId == "...", not account.segmentId == "...".A rule that reads a context field the request does not carry does not match, and the other rules still run — so you do not need a presence guard for that case. When presence itself is the condition you want, write size(segment) > 0 or "risk_score" in metadata.Expression cost is bounded by
CEL_COST_LIMIT (default 10000). The check runs at compile time — on create, on expression update, and again on activate — not only at activation: an expression whose worst-case estimated cost exceeds the limit is rejected with error code 0342 (cost limit exceeded) the first time you submit it. Syntax errors surface as 0340, type errors (including an expression that does not return a boolean) as 0341, and a failure to estimate cost at all as 0345.Expression examples by use case
Here are practical examples organized by business scenario:Amount-based rules
Merchant-based rules
Account-based rules
Combined conditions
Time-based rules
Using metadata
Metadata fields are provided by your integration. Design your payload to include the context your rules need.
Actions
Actions determine the decision when an expression evaluates to true:Scopes
Scopes define which transactions a rule applies to. A rule with noscopes is global and evaluates against every transaction. A rule with one or more scope objects evaluates only when the transaction matches at least one of them (OR semantics across scope objects).
Within a single scope object, the supported fields are:
segmentId- Match transactions from a specific segmentportfolioId- Match transactions from a specific portfolioaccountId- Match transactions from a specific accountmerchantId- Match transactions to a specific merchanttransactionType- Match specific transaction types (CARD, WIRE, PIX, CRYPTO)subType- Match specific subtypes (debit, credit, instant, etc.)
- Within one scope object: fields combine with AND. A field that is not specified is treated as a wildcard (matches any value). At least one field must be set — empty scope objects (
{}) are rejected with error code0358. - Across multiple scope objects on the same rule: they combine with OR. The rule matches if any scope object matches the transaction.
transactionType: CARD and another targeting transactionType: PIX — runs for both card and Pix transactions. A single scope with both segmentId AND accountId requires the transaction to match the segment AND the account.
Rule lifecycle
Rules progress through a defined lifecycle to ensure safe deployment.
Figure 2. Rules lifecycle and status transitions
States
Transitions
Active rules must be deactivated before deletion. This prevents accidental removal of rules that are currently being evaluated.
Create a rule
Create rules using
POST /v1/rules. Rules are created in DRAFT status by default.
A rule requires:
- name: A descriptive name, unique within its context. The context is derived from the rule’s scopes (the lowest
segmentIdacross them); rules with no scope share a single global context. So the same rule name can coexist across two different segments, but not twice inside one. Comparison is case-insensitive and ignores repeated whitespace; a collision returns409 Conflictwith error code0441. Tracer keeps the name in a normalized form, so thenameit returns can differ from the string you sent — reference the rule by theruleIdin the response. - expression: A CEL expression that evaluates to true or false
- action: The decision to return when the expression matches (ALLOW, DENY, or REVIEW)
- scopes (optional): Limit which transactions the rule applies to
Activate and deactivate rules
After creating a rule, activate it to start evaluation. Deactivate rules to stop evaluation without deleting them.
Deactivating a rule preserves it for audit purposes. Use delete only when you want to permanently remove a rule.
List and query rules
Query rules for management and auditing using
GET /v1/rules.
Query parameters
Get a specific rule
UseGET /v1/rules/{id} to retrieve the full rule definition including expression and scopes.
Update a rule
Update rules using
PATCH /v1/rules/{id}. Rules can be updated in any status, with one important restriction:
Delete a rule
Delete rules that are no longer needed. Only DRAFT and INACTIVE rules can be deleted. ACTIVE rules must be deactivated first.
Best practices
Follow these practices for effective, maintainable rules.
Naming
- Use descriptive names - The name should clearly state what the rule does
- Include context - Mention the scenario or transaction type
- Avoid abbreviations - Prefer clarity over brevity
Expression design
- Keep expressions simple - Complex logic is harder to maintain
- Use scopes for filtering - Don’t repeat scope conditions in expressions
- Test edge cases - Consider boundary values and null fields
Lifecycle management
- Start in DRAFT - Test before activating
- Return to DRAFT before editing the expression - The expression is immutable in ACTIVE and INACTIVE; move the rule to DRAFT via
POST /v1/rules/{id}/draftto edit, then reactivate - Archive unused rules - Keep audit trail intact
- Delete only when certain - Deletion is permanent
Monitoring
- Review matched rules - Check which rules are triggering
- Monitor DENY rates - High deny rates may indicate overly aggressive rules
- Audit regularly - Ensure rules still align with business requirements
Quick reference
Key endpoints, actions, and status information.

