Decision Record
Available on platform v7.7.0+. The list endpoint and policy_version_at_decision field require v1.1.
AxonFlow records every policy decision (allow / deny / require_approval) the platform makes, and exposes a slim listing surface alongside the per-decision explainability endpoint. Together they form the decision-oriented execution record — enough structure for an operator, 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.
The two surfaces
| Surface | Purpose | When 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 decision | After 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=deny" \
-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": "deny",
"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
| Param | Type | Effect |
|---|---|---|
since | RFC3339 timestamp | Only decisions after this time. Defaults to the start of your tier's window (see below). |
decision | allow | deny | require_approval | Exact-match the decision outcome. |
policy_id | string | Exact-match a single policy_id within policy_details.policy_ids. |
tool_signature | string | Exact-match the tool the decision was scoped to (e.g. postgres.query, slack.send). |
limit | integer | Capped 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.
| Tier | Window (lookback) | Max page size |
|---|---|---|
| SaaS Free | last 24h | 5 |
| SaaS Pro ($9.99 / 90d) | last 30d | 100 |
| Self-host Community | last 24h | 5 |
| Self-host Evaluation | last 14d | 100 |
| Self-host Enterprise | full audit retention | 1000 |
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:
- Operator sees
policy_version_at_decision = v3on a recent block. - Operator sees
latest_policy_version = v5on the same response. - 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.
| SDK | Method | Module |
|---|---|---|
| Go | client.ListDecisions(ctx, opts) ([]DecisionSummary, error) | top-level |
| Python | await client.list_decisions(opts) -> list[DecisionSummary] | axonflow.decisions |
| TypeScript | await client.listDecisions(opts): Promise<DecisionSummary[]> | top-level |
| Java | client.listDecisions(opts): List<DecisionSummary> | top-level |
| Rust | client.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 (allow/deny/require_approval), 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
- Explainability — single-decision detail
- Session Overrides — what
override_available: trueunlocks - Audit Logging — the full event lifecycle
- Audit search API — full-history queries beyond the listing window
