Authoring is in beta. The request and response shapes may still change, and the Management API is enabled per account. Contact us to turn it on.
The three objects
Each layer is reusable by the one above: a rule can be scored by several recipes, and a recipe can be referenced by several flows. Build them bottom-up - rule, then recipe, then flow - because each references the layer below by identifier and the reference is checked when you save.
Only the flow identifier reaches your application. Everything else can be re-tuned without a deploy on your side.
Scoring
A recipe sums the weights of the rules that triggered and flags when the total reaches itsthreshold. A rule that did not trigger or that could not run do not affect the score.
Rules that could not run
A rule reading a signal that never arrived reportsNOT_EVALUATED rather than being scored as false. This is the distinction the whole model rests on: missing is not false. A recipe with at least one such rule reports partial_evidence, and a PASS carrying it is weaker evidence than a PASS without it.
The usual cause is a request that did not carry device or network signals. Passing dispatch_id from a Frontend SDK fills in most of what rules read.
Preempting rules
Setpreempts on a rule association to make that rule’s outcome the recipe’s verdict, bypassing the score - for a condition that is conclusive on its own.
The alternative, a weight above every threshold the recipe might later be given, is a statement about the recipe’s arithmetic rather than about the rule, and it silently stops being true when the threshold moves.
The weight still applies and the score is still reported; an evaluation references the preempting rule in determined_by. That field is the only thing accounting for a recipe reporting a score under its threshold and flagging anyway.
Status
status decides whether a recipe evaluates at all, and whether its verdict counts.
Go through
SHADOW. A threshold picked without seeing live traffic is a guess, and shadow scoring is the only way to find out what it would have done before it decides anything.
A flow referencing a DRAFT or INACTIVE recipe runs its other recipes and reports nothing for that one. It was not asked, which is not the same as having passed.
Writing an expression
A rule’sexpression is CEL and must return a boolean. It is compiled before it is stored, so an undeclared name or a type mismatch is refused at authoring time rather than becoming a rule that never fires.
An expression reads three kinds of name:
The two namespaced kinds are your own names, and behave the same way in any recipe declaring them:
split, contains, matches, sets.intersects, net.inCIDR, ip(), and the rest of those libraries.
A comparison against a value that never arrived does not evaluate to false; it makes the whole rule NOT_EVALUATED. Write the condition you mean and let the engine report absence - do not try to encode a fallback.
Signals
The complete list of signals available to rules you author is documented in the customer dashboard, available for you to consult once you’re logged in.Attributes and parameters
Rules can read two kinds of value the catalog knows nothing about. A recipe declares both; a rule references them namespaced.
Keys are lower snake_case, at most 64 characters, declared without the namespace, and at most 32 of each per recipe. Values are strings - the only type either supports today, so compare as text or
split your way to a list.
The namespaces are reserved, so a signal Prelude adds later can never collide with a key you declared.
Parameters are what let one rule serve two recipes tuned differently: the same blocked_countries rule, strict at checkout and lenient at signup, is one rule and two recipes.
Rules Prelude maintains
Your recipes can score over managed rules - Prelude’s own, shared by every customer - alongside yours. Name one inrules with a weight, exactly as you would your own.
They are returned without expressions, and with a description in place of a name. In an evaluation a managed rule reports its identifier, weight and outcome, and blocked_by: "missing_data" rather than identifying the signal it needed.
A managed rule reads signals only. It can’t reference attr. or param. names, and a recipe referencing one that would is refused - a shared rule pinned to one customer’s recipe is no longer shared.
Changing things safely
References are checked in both directions, which fixes the order you work in. Creating, work upward: a recipe referencing a rule that doesn’t exist is refused, and so is a flow referencing a missing recipe. Deleting, work downward: a rule a recipe scores over answers409 rule_in_use, and a recipe a flow references answers 409 recipe_in_use. Both list what still refers to them in details. Nothing refers to a flow, so a flow deletes freely.
This is refused rather than cascaded because a recipe reads its rules all or nothing. One dangling identifier fails the read, and with it every evaluation of every flow that recipe belongs to - a bad delete would take out the gate, not just the rule.
To stop a recipe deciding without unpicking anything, replace it with status: "INACTIVE". The flow keeps working and the recipe stops evaluating.
Putting it together
status: "LIVE". The flow identifier never changed, so nothing in your application has to.