Skip to main content

Managing Policies

This page is the complete reference for every policy field in AxonFlow and the two ways to manage policies: the customer portal and the policy CRUD API. Use it when you need to know exactly what a field means, whether you can change it, and how to create or edit a policy.

AxonFlow has two families of policies:

FamilyWhere it livesManaged in API asTypical use
System policies (also called static policies)The Agent/api/v1/static-policiesFast, pattern-based checks: PII detection, SQL-injection blocking, secret-leak detection
Tenant policies (also called dynamic policies)The Orchestrator/api/v1/dynamic-policiesContext-aware rules built from conditions and actions: risk, cost, user, content, and media governance

In the portal these appear as Static (read-only) and Dynamic (editable) sources. Both families share the same three-tier hierarchy - system, organization, and tenant - described in Policy Hierarchy.


Policy Field Reference

System (static) policy fields

System policies are pattern-based rules returned by /api/v1/static-policies. The table below lists every field, whether you can set it, and what it controls.

FieldTypeSettable?MeaningValid values
namestringSettableHuman-readable policy nameRequired on create; up to 255 characters
descriptionstringSettablePlain-English explanation of what the policy detects and why it existsOptional free text
categorystringSettableClassification that groups the policy (see Policy Syntax → Categories)Required on create, e.g. security-sqli, pii-global, pii-us, code-secrets, sensitive-data
patternstringSettableRegular expression matched against request contentRequired on create; Go (RE2) regular expression
actionstringSettableWhat the Agent does when the pattern matchesblock, redact, warn, log, require_approval
severitystringSettableHow serious a match islow, medium, high, critical (default medium)
priorityintegerSettableEvaluation order - higher values are evaluated firstDefault 50 for system policies
enabledbooleanSettableWhether the policy is activetrue / false (default true)
tagsarray of stringsSettableFree-form labels for grouping (for example compliance:hipaa)Optional
tierstringSettable on create onlyWhich tier the policy belongs toorganization or tenant - system is rejected by the API
organization_idstringSettable on create onlyOrganization scope for organization-tier policiesRequired when tier is organization
risk_levelstringSystem-managedRisk classification used by the session-override contract. Seeded from the policy's categorylow, medium, high, critical (default medium)
allow_overridebooleanSystem-managedWhether an administrator may relax this policy with a session override. Forced to false whenever risk_level is criticaltrue / false
idstring (UUID)Read-onlyInternal unique identifierAssigned by AxonFlow
policy_idstringRead-onlyStable string identifier (slug)Assigned by AxonFlow
tenant_idstringRead-onlyTenant the policy belongs toDerived from your credentials
versionintegerRead-onlyIncrements on every changeAssigned by AxonFlow
created_at / updated_atstring (ISO 8601)Read-onlyCreation and last-modified timestampsAssigned by AxonFlow
created_by / updated_bystringRead-onlyWho created or last changed the policyFrom the X-User-ID header
deleted_atstring (ISO 8601)Read-onlySet when a policy is soft-deletedAssigned by AxonFlow
metadataobjectRead-onlyStructured metadata attached by AxonFlowAssigned by AxonFlow

When you read system policies through /api/v1/static-policies/effective, each policy also carries override information so you can see what is actually enforced:

FieldTypeMeaning
has_overridebooleanWhether an override is in effect for this policy
override_actionstringThe action the override enforces in place of action
override_enabledbooleanThe enabled state the override enforces in place of enabled
override_expires_atstring (ISO 8601)When a temporary override reverts
override_reasonstringWhy the override was created

When has_override is true, the override's override_action and override_enabled are what the Agent enforces; otherwise the policy's own action and enabled apply. (The customer portal's unified policy view surfaces the same result as effective_action and effective_enabled fields.)

System policies are read-only

You cannot create, modify, or delete system-tier policies through the API or the portal. They are maintained centrally and form your immutable security baseline. To change how a system policy behaves, create an override (Enterprise) - see Overriding system policies. The full catalog is documented in System Policies Reference.

Tenant (dynamic) policy fields

Tenant policies are condition-and-action rules returned by /api/v1/dynamic-policies. These are the policies you author yourself.

FieldTypeSettable?MeaningValid values
namestringSettableHuman-readable policy nameRequired; 3-100 characters
descriptionstringSettablePlain-English explanation of the ruleOptional; up to 500 characters
typestringSettableThe kind of policy, which determines the icon and grouping in the portalRequired; one of content, user, risk, cost, context_aware, media, rate-limit, budget, time-access, role-access, mcp, connector
categorystringSettableClassification for filtering and reportingRequired on the /api/v1/dynamic-policies endpoint; must start with dynamic- (for example dynamic-risk, dynamic-compliance) or media- (for example media-pii, media-safety)
conditionsarraySettableThe rules that decide whether the policy matches (see Conditions)Required; at least one condition
actionsarraySettableWhat happens when all conditions match (see Actions)Required; at least one action
priorityintegerSettableEvaluation order - higher values are evaluated first0-1000
enabledbooleanSettableWhether the policy is activetrue / false
tagsarray of stringsSettableFree-form labels for groupingOptional
tierstringSettable on create onlyWhich tier the policy belongs toorganization or tenant - system is rejected by the API
idstring (UUID)Read-onlyInternal unique identifierAssigned by AxonFlow
tenant_idstringRead-onlyTenant the policy belongs toDerived from your credentials
organization_idstringRead-only (set from tier scope)Organization scope for organization-tier policiesAssigned by AxonFlow
versionintegerRead-onlyIncrements on every changeAssigned by AxonFlow
created_at / updated_atstring (ISO 8601)Read-onlyCreation and last-modified timestampsAssigned by AxonFlow
created_by / updated_bystringRead-onlyWho created or last changed the policyFrom the X-User-ID header
deleted_atstring (ISO 8601)Read-onlySet when a policy is soft-deletedAssigned by AxonFlow

Conditions

A condition is an object with three keys. All conditions in a policy must match for the policy to trigger.

{ "field": "risk_score", "operator": "greater_than", "value": 0.8 }
KeyMeaning
fieldThe request attribute to inspect
operatorHow to compare the field to value
valueThe value (or list of values) to compare against

Supported field values:

query, response, user.email, user.role, user.department, user.tenant_id, risk_score, request_type, connector, cost_estimate, the media-governance fields (media.has_faces, media.face_count, media.has_biometric_data, media.nsfw_score, media.violence_score, media.content_safe, media.document_type, media.is_sensitive_document, media.has_pii, media.pii_types, media.has_extracted_text, media.extracted_text_length), and the retry-aware workflow fields (step.gate_count, step.completion_count, step.prior_completion_status, step.prior_output_available, step.last_decision, step.first_attempt_age_seconds, step.idempotency_key).

Retry-aware fields require Evaluation or Enterprise

Policies that reference any step.* field require an Evaluation or Enterprise license. Creating, updating, or importing such a policy on the Community edition returns a tier error.

Supported operator values:

equals, not_equals, contains, not_contains, contains_any, regex, greater_than, less_than, in, not_in.

Operator semantics

From platform v9.19.0, every engine that evaluates a tenant policy runs the same matcher, so an operator behaves identically wherever it is evaluated: the MCP planes, the LLM plane, the plan and workflow planes, and the Test preview. Five semantics were previously inconsistent between engines and are now settled:

OperatorSettled behaviorWhat changed
contains, not_containsCase-insensitive everywhere.The MCP plane previously required an exact-case match, so a condition written in one case did not match content in another.
contains_anyEvery item in the value list is compared as text, whatever its JSON type.The database-backed engine previously skipped non-string list items silently, so a list mixing numbers with strings under-matched with no signal.
greater_than, less_thanA numeric string parses and compares. An operand that is neither a number nor a parseable numeric string is not comparable, and the condition is false.One engine never compared numeric strings at all; another coerced any unparseable value to 0, so a non-numeric field value under less_than 100 matched. Neither behavior survives.
regexThe value must be a string. A non-string value is rejected at write time and does not match at evaluation time.One engine used to stringify a non-string value before compiling it, so 1.5 became the pattern 1.5 in which . is a wildcard, not a literal dot.
All ten operatorsImplemented at every call site.Some planes implemented as few as four. A policy authored with an operator that plane did not implement was inert, and there was no signal that it never fired.
Read this before upgrading if you rely on tenant policies

Most of these changes make policies match more than they used to, which for a blocking policy means more requests are denied: the case-insensitive contains, the stringified contains_any items, numeric-string comparison, and every operator becoming real at every call site. Two go the other way and make a policy match less: a non-string regex value no longer compiles, and an unparseable operand under greater_than / less_than no longer coerces to 0 and satisfies a comparison it never should have.

Before upgrading, review any tenant policy that uses contains or not_contains on the MCP plane, mixes types in a contains_any list, compares a field that can hold non-numeric text with greater_than or less_than, or uses an operator on a plane where it appeared to do nothing. Preview Impact and Simulate Policies are the safe way to check before you enable anything.

A policy with no conditions

Two different "no conditions" shapes exist in stored policies and they do not mean the same thing:

Stored shapeMeaningCan you author it?
conditions is JSON null, or the key is absentApplies to everything. Vacuously true, the way an empty WHERE clause matches every row. This is the platform's own shape for a condition-less system policy.No. The API requires at least one condition on the policies you author.
conditions is an explicitly empty array []Excluded from evaluation entirely. No enforcement plane applies it, and it does not appear in the MCP policy listings.No, and no released version could create one: POST always rejected an empty list.
Behavior change: stored empty-array conditions stop evaluating

A released gap in the update API let a PUT clear a policy's conditions to [], so a database can hold rows in that shape even though nothing could create one. From platform v9.19.0 those rows are excluded from evaluation by the database-backed policy engines on every plane (and from v10.0.0 there is no other engine: the in-memory fallback was removed), and every exclusion is counted on axonflow_policy_condition_unevaluable_total{reason="empty_conditions",plane}.

This is the safe direction. Under the alternative reading an [] row would have armed as match-everything at upgrade, turning a row with a block action into a tenant-wide denial nobody authored.

What to do: after upgrading, check the counter. If it is non-zero, list your tenant policies and look for rows with an empty conditions list. They are residue, not policy: give each one real conditions through an update, or delete it. Such rows deliberately still appear in the policy CRUD listings and in the portal, because you cannot remediate what you cannot see. Only the evaluation surfaces exclude them.

Test on one of these rows reports the exclusion and the remediation rather than pretending it matched, so the preview and the engines agree.

Actions

An action is an object with a type and an optional config:

{ "type": "redact", "config": { "fields": ["ssn", "salary"] } }

The canonical action vocabulary accepted by the policy API is:

typeWhat it doesTypical config
blockReject the request{ "reason": "…" }
redactMask the listed fields in the response{ "fields": ["field1", "field2"] }
require_approvalPause for human approval (HITL)-
warnAllow the request but record a warning-
logAllow the request and record an audit entry-
alertSend an alert to your monitoring systemVaries by alerting integration
routeApply an LLM routing ruleVaries by routing rule
modify_riskAdjust the computed risk score{ "modifier": 1.5 }

allow is the implicit default when no policy matches; it is not something you set as an action type.

How risk is expressed

A common point of confusion is how "risk" maps to fields. AxonFlow models risk in three distinct ways, and none of them is a free-form numeric field you set on a policy:

  • risk_score is a request attribute computed at evaluation time (a value from 0.0 to 1.0). You reference it in a condition, for example { "field": "risk_score", "operator": "greater_than", "value": 0.8 }.
  • modify_risk is an action that nudges that computed score up or down for downstream policies.
  • risk_level is a system-assigned classification (low / medium / high / critical) on system policies that drives the session-override contract. It is not settable through the API.

The risk-scored example below shows the condition-based pattern in practice.


Managing policies in the portal

The Policies page in the customer portal gives you a single view of every system and tenant policy. (In an in-VPC deployment the page is titled Platform Policies and changes apply across all tenants.)

The summary and the list

At the top, four cards summarize your policies: Total Policies, Static (read-only), Dynamic (editable), and Enabled. The Enabled card counts the effective enabled state, the same value the row badges show, so a system policy that an override has disabled is not counted as enabled. All four cards, the pagination total, and the rows are computed from one filtered set, so the cards always describe exactly the list beneath them.

Below them, each policy row shows:

  • the policy name and a version badge (for example v3);
  • a 🔒 lock icon when the policy is read-only (system-managed);
  • a source badge (Static or Dynamic) and a tier badge (System, Organization, or Tenant);
  • for system policies, the category and severity badges plus a preview of the regex pattern;
  • for tenant policies, the type badge and the priority;
  • an Overridden badge when an override is active, and a Disabled badge when the policy is off;
  • the description and the last-updated time.

Filtering

Use the filter bar to narrow the list by Source (All / Static / Dynamic), Tier (System / Organization / Tenant), Status (Enabled / Disabled), and - depending on the source - Type (for dynamic policies) or Category (for static policies).

Every filter is applied at one place over the combined set of system and tenant policies, so a filter narrows both halves of the list consistently and the summary cards and pagination total move with it. Status filters on the same effective enabled state the cards count.

What you can edit

ActionSystem (static) policiesTenant (dynamic) policies
CreateNot available (system-managed)Create Policy button
EditNot availableEdit button (name, description, type, conditions, actions, priority)
Enable / DisableNot availableEnable / Disable button
DeleteNot availableDelete button (with confirmation)
TestNot availableTest button
Preview ImpactNot availablePreview Impact button
Version historyNot availableHistory button
OverrideOverride / Remove Override (Enterprise)Not applicable

System policies show a System managed label instead of edit controls.

Creating or editing a tenant policy

  1. Click Create Policy (or Edit on an existing tenant policy).
  2. Give the policy a name and an optional plain-English description.
  3. Choose a type (content, user, risk, cost, …) and set a priority.
  4. Add one or more conditions (field, operator, value) and one or more actions (block, redact, require approval, …).
  5. Save. The policy takes effect within seconds.

Testing and previewing

  • Test runs the policy against a sample query and tells you whether it matched, whether the request would be blocked, which actions would fire, and the evaluation time.
  • Preview Impact estimates how the policy would affect recent traffic before you enable it.
  • Simulate Policies (top of the page) runs a what-if evaluation across your policy set.

Overriding system policies

Because system policies are read-only, Enterprise customers change their behavior with an override rather than an edit:

  1. Click Override on a system policy.
  2. Optionally choose an Action Override (you can only make a policy more restrictive, or disable it) and/or an Enabled Override.
  3. Enter a required Override Reason for the audit trail, and optionally an Expiration Date for a temporary override.
  4. Save. The policy now shows an Overridden badge and its effective action. Use Remove Override to revert.

Import and export

Export downloads your tenant policies as JSON. Import uploads a JSON file and lets you choose how to handle duplicates (skip, overwrite, or error). Up to 100 policies can be imported at once.


Managing policies via the API

Authentication and base URL

Policy endpoints are exposed by the Agent (default port 8080) and authenticated with your client credentials:

Authorization: Basic <base64(CLIENT_ID:CLIENT_SECRET)>

Your tenant is derived from those credentials. On mutating requests, add X-User-ID so changes are attributed in the audit trail.

Endpoints

Tenant (dynamic) policies - author your own condition-and-action rules:

MethodPathPurpose
GET/api/v1/dynamic-policiesList policies
POST/api/v1/dynamic-policiesCreate a policy
GET/api/v1/dynamic-policies/{id}Get one policy
PUT/api/v1/dynamic-policies/{id}Update a policy
DELETE/api/v1/dynamic-policies/{id}Delete a policy (soft delete)
POST/api/v1/dynamic-policies/{id}/testTest a policy against sample input
GET/api/v1/dynamic-policies/{id}/versionsList version history
POST/api/v1/dynamic-policies/importBulk import
GET/api/v1/dynamic-policies/exportBulk export
GET/api/v1/dynamic-policies/effectiveEffective policies for the tenant

System (static) policies - read the baseline, manage your own custom pattern rules:

MethodPathPurpose
GET/api/v1/static-policiesList policies
POST/api/v1/static-policiesCreate a custom pattern policy
GET/api/v1/static-policies/{id}Get one policy
PUT/api/v1/static-policies/{id}Update a non-system policy
DELETE/api/v1/static-policies/{id}Delete a non-system policy
PATCH/api/v1/static-policies/{id}Toggle enabled
POST/api/v1/static-policies/testTest a regex pattern against inputs
GET/api/v1/static-policies/effectiveEffective policies with overrides applied

See Tenant Policy API and System Policy API for the full wire reference. The settable-vs-read-only split is exactly as listed in the field reference above.

Example 1: a custom PII policy

Create a tenant policy that redacts sensitive fields whenever a request mentions them. This is a content policy in the dynamic-compliance category that fires a redact action.

curl -X POST http://localhost:8080/api/v1/dynamic-policies \
-H "Content-Type: application/json" \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "X-User-ID: [email protected]" \
-d '{
"name": "Redact customer PII",
"description": "Mask SSN, salary, and medical record fields in responses",
"type": "content",
"category": "dynamic-compliance",
"priority": 900,
"enabled": true,
"conditions": [
{ "field": "query", "operator": "contains_any", "value": ["ssn", "salary", "medical_record"] }
],
"actions": [
{ "type": "redact", "config": { "fields": ["ssn", "salary", "medical_record"] } }
]
}'

Response (201 Created):

{
"policy": {
"id": "6f1c2e8a-9b3d-4f7a-8c21-2d4e6a8b0c11",
"name": "Redact customer PII",
"description": "Mask SSN, salary, and medical record fields in responses",
"type": "content",
"category": "dynamic-compliance",
"tier": "tenant",
"priority": 900,
"enabled": true,
"version": 1,
"conditions": [
{ "field": "query", "operator": "contains_any", "value": ["ssn", "salary", "medical_record"] }
],
"actions": [
{ "type": "redact", "config": { "fields": ["ssn", "salary", "medical_record"] } }
],
"created_at": "2026-06-12T10:00:00Z",
"updated_at": "2026-06-12T10:00:00Z"
}
}

List it back, filtering by category:

curl "http://localhost:8080/api/v1/dynamic-policies?category=dynamic-compliance" \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)"

Test it against a sample query before relying on it:

curl -X POST http://localhost:8080/api/v1/dynamic-policies/6f1c2e8a-9b3d-4f7a-8c21-2d4e6a8b0c11/test \
-H "Content-Type: application/json" \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-d '{ "query": "Show me the salary for employee 42" }'
{
"matched": true,
"blocked": false,
"actions": [
{ "type": "redact", "config": { "fields": ["ssn", "salary", "medical_record"] } }
],
"explanation": "Policy 'Redact customer PII' matched: all 1 conditions evaluated to true",
"eval_time_ms": 0.21
}

To make the rule stricter later, update it (only the fields you send change):

curl -X PUT http://localhost:8080/api/v1/dynamic-policies/6f1c2e8a-9b3d-4f7a-8c21-2d4e6a8b0c11 \
-H "Content-Type: application/json" \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "X-User-ID: [email protected]" \
-d '{ "priority": 950 }'

And remove it when it is no longer needed:

curl -X DELETE http://localhost:8080/api/v1/dynamic-policies/6f1c2e8a-9b3d-4f7a-8c21-2d4e6a8b0c11 \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "X-User-ID: [email protected]"

A successful delete returns 204 No Content.

Example 2: a risk-scored block policy

Block any request whose computed risk score exceeds a threshold. This is a risk policy that reads the risk_score request attribute in a condition and fires a block action.

curl -X POST http://localhost:8080/api/v1/dynamic-policies \
-H "Content-Type: application/json" \
-H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
-H "X-User-ID: [email protected]" \
-d '{
"name": "Block high-risk queries",
"description": "Reject requests whose risk score exceeds the safety threshold",
"type": "risk",
"category": "dynamic-risk",
"priority": 1000,
"enabled": true,
"conditions": [
{ "field": "risk_score", "operator": "greater_than", "value": 0.8 }
],
"actions": [
{ "type": "block", "config": { "reason": "Query risk score exceeds safety threshold" } }
]
}'

The policy matches when the request's risk_score is above 0.8 and blocks it. You can combine the risk condition with others - for example, only block high-risk requests from a specific role by adding { "field": "user.role", "operator": "equals", "value": "contractor" } to the conditions array (remember: all conditions must match).

Validation and errors

Validation failures return 400 with a VALIDATION_ERROR code and per-field details:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{ "field": "name", "message": "Name must be between 3 and 100 characters" },
{ "field": "actions[0]", "message": "invalid action type: deny" }
]
}
}

Common rules to keep in mind:

  • name is required (3-100 characters); description is capped at 500 characters.
  • type must be one of the values in the field reference.
  • At least one condition and one action are required; priority must be 0-1000.
  • Each action type must be in the canonical action vocabulary.
  • Attempting to create a system-tier policy returns 403 (System policies cannot be created via API).

Editions and limits

  • System-tier policies are immutable on every edition - you override them (Enterprise) rather than edit them.
  • Organization-tier policies require an Evaluation or Enterprise license.
  • Tenant-tier policies are available on every edition; Community and Evaluation have a per-tenant policy-count limit, while paid editions are unlimited. Exceeding a limit returns a tier error with an upgrade link.

Compare what each edition unlocks in Community vs Evaluation vs Enterprise.