Skip to main content

Decision Record

Reference page. This documents the two read APIs for inspecting past decisions: the list surface and the per-decision explain surface. To understand the decisions themselves first, see Decision Explainability and Audit Logging.

AxonFlow records every policy decision the platform makes and exposes a slim listing surface alongside the per-decision explainability endpoint. On these read surfaces a decision verdict is one of allowed, blocked, redacted, needs_approval, or error. Together they form the decision-oriented execution record: enough structure for an admin, another agent, or future-you to reconstruct why a workflow's outcome changed.

This is different from a raw audit log. Audit logs are a chronological dump; the decision record is the control-plane reasoning trail: which policy fired, at which version, with what risk level, and whether an override could unblock it.

Available on platform v7.7.0+. The list endpoint and policy_version_at_decision field require v1.1.

The two surfaces

SurfacePurposeWhen to use
GET /api/v1/decisions (list)Show me the most recent decisions for my tenant"What just got blocked?" UX in plugins, dashboards, alerts.
GET /api/v1/decisions/:id/explain (single)Explain this specific decisionAfter a block, when the user wants to understand or appeal.

Both surfaces share the same security model: the caller's X-Tenant-ID is enforced at the SQL WHERE level, not as a post-fetch comparison. There is no enumeration oracle.

Listing recent decisions

curl -u "$CLIENT_ID:$CLIENT_SECRET" \
"https://your-platform/api/v1/decisions?limit=20&decision=blocked" \
-H "X-Tenant-ID: $TENANT_ID"

Response (slim summary, NOT the full DecisionExplanation):

{
"decisions": [
{
"decision_id": "dec_wf123_step4",
"timestamp": "2026-05-07T12:16:36Z",
"decision": "blocked",
"policy_id": "sys_sqli_drop_table",
"tool_signature": "postgres.query"
}
]
}

To get the full reason and matched-rule detail for any one of these, call the explain endpoint with the decision_id.

Filters

ParamTypeEffect
sinceRFC3339 timestampOnly decisions after this time. Defaults to the start of your tier's window (see below).
decisionallowed | blocked | redacted | needs_approval | errorExact-match the decision outcome. Must be a canonical value; legacy allow/deny are rejected with 400.
policy_idstringExact-match a single policy_id within policy_details.policy_ids.
tool_signaturestringExact-match the tool the decision was scoped to (e.g. postgres.query, slack.send).
limitintegerCapped per tier (see below).

Tier-gated window and page size

The list surface is intentionally tiered: Free users see the most recent block, Pro users get the full audit-retention window so they can actually browse the decision record over time.

TierWindow (lookback)Max page size
SaaS Freelast 24h5
SaaS Pro ($9.99 / 90d)last 30d100
Self-host Communitylast 24h5
Self-host Evaluationlast 14d100
Self-host Enterprisefull audit retention1000

Note: the listing window is bounded by your tier's audit retention. Pro's "last 30d" matches Pro's 30-day audit retention exactly; the listing surface cannot return decisions older than the audit row itself.

Hitting the page-size cap

When a Free tier user requests limit > 5 (or sees more than 5 decisions in the window), the response is 429 with the standard upgrade envelope:

{
"error": "decision list page limit reached for your tier",
"limit_type": "decision_list_size",
"tier": "Free",
"upgrade": {
"wording": "Free returns the most-recent 5 decisions in the last 24h. Pro returns 100 across 30 days.",
"compare_url": "https://getaxonflow.com/pro",
"buy_url": "https://buy.stripe.com/..."
}
}

Plugins and host CLIs surface this as a clear "upgrade to Pro to see your full block history" prompt rather than silently truncating. The envelope shape matches the rate-limit-429 envelope returned by the daily-quota cap, so a plugin can use a single parse path for both.

Explaining a specific decision

For the full structured explanation of any decision_id (matched policies, matched rules, risk level, override availability, historical hit count), see /docs/governance/explainability.

The explain endpoint surfaces two V1.1 fields that the list endpoint omits for brevity:

  • policy_version_at_decision — the version of the matched policy that was applied when this decision was made.
  • latest_policy_version — the current head of the same policy.

Together these answer the "why is this blocked NOW that wasn't 2 days ago?" question without a separate diff endpoint:

  1. Operator sees policy_version_at_decision = v3 on a recent block.
  2. Operator sees latest_policy_version = v5 on the same response.
  3. Operator now knows the policy has changed since the user's last successful run.

Rule-level diff between two policy versions is on the V1.2 roadmap (GET /api/v1/decisions/:id/policy-version-diff).

SDK methods

All five SDKs ship explain_decision today (axonflow-sdk-go, axonflow-sdk-python, axonflow-sdk-typescript, axonflow-sdk-java, axonflow-sdk-rust). The list_decisions companion lands across all five SDKs as part of the V1.1 release train.

SDKMethodModule
Goclient.ListDecisions(ctx, opts) ([]DecisionSummary, error)top-level
Pythonawait client.list_decisions(opts) -> list[DecisionSummary]axonflow.decisions
TypeScriptawait client.listDecisions(opts): Promise<DecisionSummary[]>top-level
Javaclient.listDecisions(opts): List<DecisionSummary>top-level
Rustclient.list_decisions(opts).await -> Vec<DecisionSummary>top-level

Each follows the cross-SDK naming convention locked in ADR-043 §"SDK parity".

MCP tool parity

The list_recent_decisions MCP tool is served by the agent's MCP server at /api/v1/mcp-server, alongside the existing explain_decision tool. Every plugin (OpenClaw, Claude Code, Cursor, Codex) gets it for free — no per-plugin registration needed.

Arguments: an optional since (RFC3339), decision (canonical allowed/blocked/redacted/needs_approval/error), limit (capped per tier). Returns the same DecisionSummary[] shape as the HTTP endpoint.

Authorization

Same model as explain_decision: the caller's X-Tenant-ID is required and enforced at the SQL WHERE level. Cross-tenant listing returns 403; an unauthenticated request returns 401.

Within a tenant, the listing returns every decision the tenant has authority over (matching the same scope as audit search).

See also

Rollout Checklist

Use this page as one layer of the broader governance rollout: