Skip to main content

Approval Rules Configuration

Rules decide who must approve a purchase request, in what order, from the amount, cost center, category, supplier and other properties of the request. The same engine produces approval plans for purchase requests and for credit-card limit increases.

:::warning Author rules in the dashboard, not through the API

rule_content is a GoRules ZEN decision graph, not a hand-writable schema. It is produced by the rule editor under Administration → Rules, which is the only supported way to author one.

The API stores rule_content as opaque JSON and validates only that it is JSON. A body that is well-formed JSON but not a valid ZEN graph is accepted by POST /rules and then matches nothing at evaluation time, with no error anywhere. A rule that silently never fires on the surface that gates spend approval is the worst failure mode this API has, so treat rule bodies as editor output that you move around, never as something you compose.

:::

What the engine is​

Evaluation is delegated to GoRules ZEN (gorules/zen-go). A rule body is a JDM graph: a set of nodes, of which the interesting one is a decision table. Each table has input columns (the conditions) and output columns (the result). A row whose input cells all match contributes its output cells.

For an approval rule the output column that matters is named approver (receiver for order-confirmation tables). Its cell holds either a concrete user code or one of the role selectors:

SelectorResolves to
:cost_center_ownerThe owner of record of the request's cost center
:org_unit_leaderThe leader, or deputy leader, of the relevant org unit
:managerThe requester's direct manager
:manager_chain[n]The n-th manager up the requester's reporting line

The selectors are symbolic on purpose: a rule referring to :cost_center_owner always resolves to the current owner, so changing the owner does not require touching the rule.

Rule entity​

The wrapper around rule_content is a normal REST resource:

{
"name": "PR over 5k EUR, CFO sign-off",
"rule_type": "approval",
"priority": 100,
"rule_content": { "nodes": [ /* editor output */ ] },
"tags": ["procurement", "finance"]
}
FieldNotes
rule_typeOne of approval, procurement, validation, pricing, order_confirmation, limit_increase. A limit_increase rule can never fire on an ordinary purchase request, and vice versa.
priorityLower number wins on tie-breaks. Defaults to 100.
rule_contentOpaque JSON to the API; a ZEN graph to the engine.
tagsFree-form labels, a text[] column.
is_activeNot settable on create; it defaults to true. Change it through PUT /rules/{code}.

PUT bumps version. DELETE is a soft delete.

Endpoints​

GET /api/v1/rules
POST /api/v1/rules
GET /api/v1/rules/{code}
PUT /api/v1/rules/{code}
DELETE /api/v1/rules/{code}
POST /api/v1/rules/simulate
GET /api/v1/rules/executions

GET /rules/executions is the audit trail of evaluations.

Simulating​

Exactly one of input or purchase_request_code must be set.

Prefer purchase_request_code: it builds the same input the production evaluation would build from a real request, so it exercises the derivation as well as the table. This is what the dashboard simulator uses.

{
"rule_type": "approval",
"purchase_request_code": "PR-1042"
}

Add rule_code alongside it to scope the simulation to one rule set instead of the request's whole flow.

The response is a batch evaluation result carrying context, total_rules, successful_rules, failed_rules, total_time_ms, derived_input, scope, and a results array holding each rule's raw output. Note what it does not contain: there is no resolved per-step approver list. To see the plan a request would get, read the plan itself through the approvals endpoints after the request is created.

Behavior worth knowing​

  • Active rules apply to new plans only. In-flight approval plans are not re-evaluated when a rule changes.
  • Inactive rules are skipped at evaluation but remain simulatable, which is how you validate a change before turning it on.
  • Composition is over decision-table rows, not over a merge of separate rule documents. Two rules matching the same request each contribute their outputs; how those combine is the graph's business, not a documented merge order.