Skip to main content

Unified Policy API

The Unified Policy API gives the Customer Portal one source-agnostic surface to read and write policies across both planes AxonFlow governs:

  • static policies — pattern/regex based, evaluated by the Agent (see the System Policy API)
  • dynamic policies — condition based, evaluated by the Orchestrator (see the Tenant Policy API)

Instead of the caller choosing an endpoint per policy plane, the unified API resolves each policy's source automatically and dispatches to the correct underlying service. Auth, tenant/organization scoping, and behavior are identical to calling the per-plane endpoints directly — the unified API is a thin dispatcher, not a new policy engine or a merged schema.

API Endpoint: /api/v1/unified-policies (Customer Portal)

Edition

The Customer Portal and its unified policy surface are an Enterprise feature. The underlying per-plane APIs are available in all editions.

Why a unified surface

Historically the portal UI had to know, per policy, whether it was static (Agent) or dynamic (Orchestrator) and call a different write endpoint accordingly. That entrenched the split in every client. The unified API removes that: read and write flow through one path, and the source is resolved server-side using the same lookup for reads and writes, so a policy is never classified two different ways.

The unified surface deliberately does not merge the two policy schemas — a static policy still has a pattern/severity, a dynamic policy still has conditions/actions. Merging the write schema and renaming the tier terminology are tracked separately and are not part of this API.

Read endpoints

Method & PathPurpose
GET /api/v1/unified-policiesList policies from both sources, source-tagged, paginated
GET /api/v1/unified-policies/summaryCounts by source, tier, category, severity
GET /api/v1/unified-policies/effectiveEffective policies with overrides applied
GET /api/v1/unified-policies/{id}A single policy from whichever source owns it

Every policy in a response carries a source field ("static" or "dynamic"). List/summary/effective responses also carry partial: true and source_errors when one source could not be reached, so an unavailable plane is surfaced rather than silently dropped.

Write endpoints

Method & PathSource resolutionDispatches to
POST /api/v1/unified-policiessource field in the body (required)static → Agent create; dynamic → Orchestrator create
PUT /api/v1/unified-policies/{id}resolved from the idAgent / Orchestrator update
DELETE /api/v1/unified-policies/{id}resolved from the idAgent / Orchestrator delete
POST /api/v1/unified-policies/{id}/toggleresolved from the idenable/disable on the owning plane
POST|GET|DELETE /api/v1/unified-policies/{id}/overrideresolved from the idAgent static override only

Source resolution

For id-addressed verbs (update / delete / toggle / override) the source is resolved by looking the id up on both planes (tenant-scoped, static preferred if both match) — the same lookup the read endpoints use. Resolution is fail-closed:

  • found on exactly one plane → dispatched there
  • absent from both planes → 404, no write
  • a plane could not be reached, so ownership is unknown → 502, no write (the dispatcher never guesses a plane)

For POST (create) there is no id to resolve, so the body must include a source of "static" or "dynamic". A missing or unknown source is rejected with 400.

Create

# Create a dynamic (condition-based) tenant policy
curl -X POST https://portal.example.com/api/v1/unified-policies \
-H 'Content-Type: application/json' --cookie "$SESSION" \
-d '{
"source": "dynamic",
"name": "Block risky queries",
"type": "risk",
"tier": "tenant",
"conditions": [{"field": "risk_score", "operator": "greater_than", "value": 0.8}],
"actions": [{"type": "block"}],
"enabled": true
}'

# Create a static (pattern-based) tenant policy
curl -X POST https://portal.example.com/api/v1/unified-policies \
-H 'Content-Type: application/json' --cookie "$SESSION" \
-d '{
"source": "static",
"name": "Internal ticket IDs",
"category": "pii-global",
"tier": "tenant",
"pattern": "TICKET-[0-9]+",
"action": "redact",
"severity": "low",
"enabled": true
}'

Every field other than source is passed through unchanged to the underlying create endpoint, which owns the real schema (see the System Policy API and Tenant Policy API for the full field sets).

Update, delete, toggle

# Update — source resolved from the id
curl -X PUT .../api/v1/unified-policies/{id} -d '{"description": "…"}'

# Toggle enable/disable — body carries the target state
curl -X POST .../api/v1/unified-policies/{id}/toggle -d '{"enabled": false}'

# Delete
curl -X DELETE .../api/v1/unified-policies/{id}

Override (static-only)

Overrides apply to system/static policies only: a system policy's pattern is immutable, so an override adjusts its action or enabled state without changing the pattern. Dynamic/tenant policies are changed directly with update/toggle instead — an override request against a dynamic policy is rejected with 400.

# Override a system static policy's action (Enterprise)
curl -X POST .../api/v1/unified-policies/{system-policy-id}/override \
-H 'Content-Type: application/json' --cookie "$SESSION" \
-d '{"action_override": "warn", "override_reason": "Monitoring false positives"}'

# Read / revoke the override
curl -X GET .../api/v1/unified-policies/{system-policy-id}/override
curl -X DELETE .../api/v1/unified-policies/{system-policy-id}/override

See Overrides API and Governance → Overrides for override semantics, expiry, and the least-permissive rules.

Relationship to the per-plane APIs

The unified API dispatches to — it does not replace — the per-plane endpoints:

PlaneDirect APIService
static/api/v1/static-policiesAgent
dynamic/api/v1/policiesOrchestrator

The direct endpoints remain available for backward compatibility. New portal-facing write integrations should target /api/v1/unified-policies/*. The direct write routes are candidates for deprecation once clients migrate and the system/tenant API terminology alignment lands.