Skip to main content
Guardrail rules are tenant-global business constraints that the Recommend engine enforces on every decision. Unlike decisioning gates (which are attached to individual offers) and contact policies (which throttle frequency), guardrails apply across the whole candidate set. See Enforcement at decision time for the runtime semantics.

POST /api/v1/guardrails

Create a guardrail rule.

Request Body

Example

Response


GET /api/v1/guardrails

List all guardrails for the tenant.

Response

Returns an array of guardrail objects.

PUT /api/v1/guardrails

Update a guardrail by key. Supports optimistic concurrency via rowVersion.

Request Body


DELETE /api/v1/guardrails

Soft-delete a guardrail by key.

Query Parameters

Response 200


Enforcement at decision time

Active guardrails run during POST /api/v1/recommend. The stage executes once per request, at rank-node entry — so ranking and the result limit operate on the survivors, and expressions can reference offer.score. Flows without a rank node fall back to running the stage at the response node. Guardrails are independent of contact policies: setting skipContactPolicy on a flow does not skip guardrails.

Expression semantics

The expressionAst describes when the rule fires — i.e. when the candidate matches the constraint the guardrail is guarding against. A leaf condition is { field, operator, value }; conditions combine with all (AND), any (OR), and not. Operators: eq, neq, gt, gte, lt, lte, in, not_in, contains, starts_with, exists.
This rule fires for a regulated offer shown to an under-18 customer.

What firing does

Rules whose status is not "active" are skipped.
A malformed or unrecognized expression — an empty AST, a missing field/operator, or an unknown operator — never fires. The evaluator returns false, so such a rule cannot block a candidate. Guardrails fail open at the candidate level: a mistyped rule degrades to a no-op rather than silently suppressing every offer.

Fail-open on load errors

If the guardrail rule lookup fails entirely, the request continues unfiltered with a loud error log — a rules-load outage never takes down the decision hot path. The active rule list is cached for 300 seconds and invalidated automatically on every guardrail create, update, or delete, so edits take effect on the next request.

Sub-flows

Each sub-flow execution (call_flow / extension_point) runs its own guardrail stage. Candidates returned by a sub-flow are already filtered within that sub-flow’s run.

Debug trace

With debug: true on the Recommend request, debugTrace.afterGuardrails is the real post-guardrail candidate count, and debugTrace.guardrailReasons[] lists each failed evaluation:
See Recommend API for the full debug trace shape.