GET /rulesets/{ruleset_id}/graph (and the composed GET /rulebooks/{rulebook_id}/graph) return the ruleset as a graph: every field, every compiled criterion, the groups they roll up into, and how those groups combine into the outcome. It’s the same compiled logic /schema and /explain describe, laid out as nodes and edges instead of a flat field list — useful for rendering a visual map, for an agent that wants to reason about rule structure programmatically, or for spot-checking that a generated ruleset compiled the shape you expected.
Public rulesets are accessible without an API key, the same as /decide and /schema — see Do you even need a key?.
Request
/ as %2F (or pass the concrete dated ruleset_id from a /decide envelope — either resolves, same as /schema and /explain).
Response shape
graph.nodes— one entry per field, criterion, group, and a single terminaloutcomenode.node.typeis one offield,criterion,group,outcome.graph.edges— directed{source, target, type}triples:field_to_criterion,criterion_to_group,group_to_outcome. Follow them to render your own layout instead of Mermaid.graph.stats— a quick summary (field/criterion/group counts, section count).shared_fieldsis nonzero only on a composed rulebook graph where two sections reference the same field.mermaid— a ready-to-render Mermaid flowchart string covering the same graph. Paste it into any Mermaid renderer, or embed it directly if your docs/UI already renders Mermaid.
Criterion nodes: the three altitudes
Eachcriterion node carries a display object with the same underlying compiled expression shown three ways, so a caller can pick the altitude it needs:
The empty/legacy case: display.routes can be null
display.routes is a best-effort structural rendering built from the same expression as sentence and expr. If a criterion’s compiled expression is shaped in a way the route-tree builder doesn’t yet cover, the server catches that internally and returns routes: null for that node — sentence and expr are always populated (they come from the same code path that already backs /explain), so a caller can always fall back to sentence for display and expr for exact logic, even when routes is absent. Never assume routes is present; branch on it being null the same way you’d branch on any optional field.
overlay — decision-time status (include_graph_overlay)
By default every node’s overlay is null — the graph above is pure structure, independent of any particular decision. Pass include_graph_overlay: true on /decide to get the same graph back with each node’s overlay stamped with that decision’s outcome, so you can render a map that’s colored by what actually happened:
graph_overlay field is the same {nodes, edges, sections, stats} shape as above, but each criterion’s overlay (and display.routes.overlay) is now {"status": "satisfied", "supporting_facts": [...]} (or not_satisfied / pending) instead of null:
Rulebook graphs (composed)
GET /rulebooks/{rulebook_id}/graph (path forms: /rulebooks/{rulebook_id}/graph and /rulebooks/{namespace}/{name}/graph) returns the same {nodes, edges, sections, stats} IR, merged across every ruleset the rulebook composes — graph.sections lists each member section, and stats.shared_fields counts fields two or more sections reference in common. Unlike ruleset-level /graph, rulebook endpoints are not anonymous — they need an API key (the same rule as rulebook_id on /decide; see Do you even need a key? and missing_api_key).
Rendering the Mermaid string
Themermaid field is plain text — feed it straight into any Mermaid-compatible renderer:
The node IDs above are shortened for readability — the live response prefixes them with the full section slug (e.g.
group_aethis/uk-fsm/child-eligibility_school_type_check) so a composed rulebook graph never collides node IDs across sections.-->, ==>) trace the compiled decision path; dotted arrows (-.->) trace which fields feed which criteria. A criterion shared by two or more sections in a composed rulebook graph is styled with the shared class (gold fill) so it’s visually obvious which parts of the map are doing double duty.
See also
- Debug a failing decide — using
include_graph_overlayandinclude_tracetogether to pinpoint a failing criterion. - Decision envelope — the full
/decideresponse contract. - Errors reference —
missing_api_keyand other failure shapes you’ll hit calling the rulebook-level endpoints anonymously.