Skip to main content

Dynamic Policy API

Manage dynamic policies for runtime governance through the Orchestrator API. Dynamic policies use condition-based rules evaluated at request time for flexible, tenant-specific governance.

Overview

Dynamic policies differ from static policies:

FeatureStatic PoliciesDynamic Policies
StructureRegex patternsConditions + Actions
LocationAgentOrchestrator
Use CasePattern matching (PII, SQLi)Business logic, risk, cost
ScopeRequest contentUser, context, cost, risk

Base URL: http://localhost:8081 (Orchestrator)

Policy Types

TypeDescription
contentEvaluate based on query/response content
userEvaluate based on user attributes
riskEvaluate based on risk scores
costEvaluate based on cost estimates

Policy Tiers

TierDescription
systemAxonFlow-managed, immutable
organizationOrganization-wide (Enterprise)
tenantTeam-specific policies

Endpoints

GET /api/v1/policies

List all dynamic policies for a tenant with optional filtering.

Request:

curl "http://localhost:8081/api/v1/policies?type=user&enabled=true&page=1&page_size=20" \
-H "X-Tenant-ID: my-tenant"

Query Parameters:

ParameterTypeDescription
typestringFilter by policy type (content, user, risk, cost)
categorystringFilter by category (dynamic-risk, dynamic-compliance, etc.)
tierstringFilter by tier (system, organization, tenant)
enabledbooleanFilter by enabled status
searchstringSearch in name and description
include_deletedbooleanInclude soft-deleted policies
sort_bystringSort field (name, created_at, updated_at)
sort_dirstringSort direction (asc, desc)
pageintegerPage number (default: 1)
page_sizeintegerItems per page (default: 20, max: 100)

Response (200 OK):

{
"policies": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"description": "Limit external API calls per user per hour",
"type": "user",
"category": "dynamic-risk",
"tier": "tenant",
"conditions": [
{
"field": "user.department",
"operator": "equals",
"value": "engineering"
},
{
"field": "cost_estimate",
"operator": "greater_than",
"value": 10.0
}
],
"actions": [
{
"type": "alert",
"config": {
"channel": "slack",
"message": "High cost request from engineering"
}
}
],
"priority": 100,
"enabled": true,
"version": 3,
"tenant_id": "my-tenant",
"created_by": "[email protected]",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-02T10:00:00Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total_items": 15,
"total_pages": 1
}
}

POST /api/v1/policies

Create a new dynamic policy.

Request:

curl -X POST http://localhost:8081/api/v1/policies \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: my-tenant" \
-H "X-User-ID: [email protected]" \
-d '{
"name": "Block High-Cost Requests",
"description": "Block requests estimated to cost more than $5",
"type": "cost",
"category": "dynamic-cost",
"tier": "tenant",
"conditions": [
{
"field": "cost_estimate",
"operator": "greater_than",
"value": 5.0
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Request exceeds cost limit of $5"
}
}
],
"priority": 100,
"enabled": true,
"tags": ["cost-control", "budget"]
}'

Request Body:

FieldTypeRequiredDescription
namestringYesPolicy display name (max 255 chars)
descriptionstringNoPolicy description
typestringYesPolicy type: content, user, risk, cost
categorystringNoCategory: dynamic-risk, dynamic-compliance, etc.
tierstringNoTier: organization, tenant (default: tenant)
conditionsarrayYesCondition rules to evaluate
actionsarrayYesActions to execute when conditions match
priorityintegerNoEvaluation priority (higher = first)
enabledbooleanNoWhether policy is active (default: true)
tagsarrayNoTags for filtering

Condition Object:

FieldTypeDescription
fieldstringField to evaluate (see Available Fields)
operatorstringComparison operator (see Operators)
valueanyValue to compare against

Action Object:

FieldTypeDescription
typestringAction type: block, redact, alert, log, route, modify_risk
configobjectAction-specific configuration

Response (201 Created):

{
"policy": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "Block High-Cost Requests",
"description": "Block requests estimated to cost more than $5",
"type": "cost",
"category": "dynamic-cost",
"tier": "tenant",
"conditions": [
{
"field": "cost_estimate",
"operator": "greater_than",
"value": 5.0
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Request exceeds cost limit of $5"
}
}
],
"priority": 100,
"enabled": true,
"version": 1,
"tenant_id": "my-tenant",
"tags": ["cost-control", "budget"],
"created_by": "[email protected]",
"created_at": "2025-01-02T10:00:00Z",
"updated_at": "2025-01-02T10:00:00Z"
}
}

GET /api/v1/policies/{id}

Get a specific policy by ID.

Request:

curl http://localhost:8081/api/v1/policies/550e8400-e29b-41d4-a716-446655440001 \
-H "X-Tenant-ID: my-tenant"

Response (200 OK):

{
"policy": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"description": "Limit external API calls per user per hour",
"type": "user",
"category": "dynamic-risk",
"tier": "tenant",
"conditions": [
{
"field": "user.role",
"operator": "not_equals",
"value": "admin"
},
{
"field": "request_type",
"operator": "equals",
"value": "mcp_query"
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Non-admin users cannot execute MCP queries"
}
}
],
"priority": 100,
"enabled": true,
"version": 3,
"tenant_id": "my-tenant",
"created_by": "[email protected]",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-02T10:00:00Z"
}
}

PUT /api/v1/policies/{id}

Update an existing policy. Creates a new version automatically.

Request:

curl -X PUT http://localhost:8081/api/v1/policies/550e8400-e29b-41d4-a716-446655440001 \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: my-tenant" \
-H "X-User-ID: [email protected]" \
-d '{
"description": "Updated: Allow admins to bypass",
"conditions": [
{
"field": "user.role",
"operator": "not_in",
"value": ["admin", "superadmin"]
}
],
"priority": 150
}'

Request Body:

All fields are optional. Only provided fields will be updated.

FieldTypeDescription
namestringPolicy display name
descriptionstringPolicy description
typestringPolicy type
categorystringPolicy category
conditionsarrayCondition rules
actionsarrayAction rules
priorityintegerEvaluation priority
enabledbooleanWhether policy is active
tagsarrayTags for filtering

Response (200 OK):

{
"policy": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"description": "Updated: Allow admins to bypass",
"type": "user",
"category": "dynamic-risk",
"tier": "tenant",
"conditions": [
{
"field": "user.role",
"operator": "not_in",
"value": ["admin", "superadmin"]
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Non-admin users cannot execute MCP queries"
}
}
],
"priority": 150,
"enabled": true,
"version": 4,
"tenant_id": "my-tenant",
"created_by": "[email protected]",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-02T12:00:00Z"
}
}

DELETE /api/v1/policies/{id}

Delete a policy permanently.

Request:

curl -X DELETE http://localhost:8081/api/v1/policies/550e8400-e29b-41d4-a716-446655440001 \
-H "X-Tenant-ID: my-tenant" \
-H "X-User-ID: [email protected]"

Response (204 No Content):

No response body on success.


POST /api/v1/policies/{id}/test

Test a policy against sample input without affecting production.

Request:

curl -X POST http://localhost:8081/api/v1/policies/550e8400-e29b-41d4-a716-446655440001/test \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: my-tenant" \
-d '{
"query": "Run database migration",
"user": {
"id": "user_123",
"email": "[email protected]",
"role": "developer"
},
"request_type": "mcp_query",
"context": {
"connector": "postgresql"
}
}'

Request Body:

FieldTypeRequiredDescription
querystringYesSample query to evaluate
userobjectNoUser attributes for evaluation
request_typestringNoRequest type being simulated
contextobjectNoAdditional context for evaluation

Response (200 OK):

{
"matched": true,
"blocked": true,
"actions": [
{
"type": "block",
"config": {
"message": "Non-admin users cannot execute MCP queries"
},
"message": "Request blocked by policy"
}
],
"explanation": "Condition matched: user.role (developer) not_equals admin",
"eval_time_ms": 1.5
}

GET /api/v1/policies/{id}/versions

Get version history for a policy.

Request:

curl http://localhost:8081/api/v1/policies/550e8400-e29b-41d4-a716-446655440001/versions \
-H "X-Tenant-ID: my-tenant"

Response (200 OK):

{
"versions": [
{
"version": 4,
"snapshot": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"conditions": [{"field": "user.role", "operator": "not_in", "value": ["admin", "superadmin"]}],
"actions": [{"type": "block", "config": {"message": "..."}}],
"priority": 150,
"enabled": true
},
"changed_by": "[email protected]",
"changed_at": "2025-01-02T12:00:00Z",
"change_type": "update",
"change_summary": "Updated conditions to exclude superadmin"
},
{
"version": 3,
"snapshot": {
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"conditions": [{"field": "user.role", "operator": "not_equals", "value": "admin"}],
"actions": [{"type": "block", "config": {"message": "..."}}],
"priority": 100,
"enabled": true
},
"changed_by": "[email protected]",
"changed_at": "2025-01-02T10:00:00Z",
"change_type": "update",
"change_summary": "Initial version"
}
]
}

Bulk Operations

POST /api/v1/policies/import

Import multiple policies from JSON. Useful for environment migration.

Request:

curl -X POST http://localhost:8081/api/v1/policies/import \
-H "Content-Type: application/json" \
-H "X-Tenant-ID: my-tenant" \
-H "X-User-ID: [email protected]" \
-d '{
"policies": [
{
"name": "Block Non-Admins from Delete",
"type": "user",
"conditions": [{"field": "user.role", "operator": "not_equals", "value": "admin"}],
"actions": [{"type": "block", "config": {"message": "Only admins can delete"}}],
"priority": 100,
"enabled": true
},
{
"name": "Alert on High Cost",
"type": "cost",
"conditions": [{"field": "cost_estimate", "operator": "greater_than", "value": 10}],
"actions": [{"type": "alert", "config": {"channel": "slack"}}],
"priority": 50,
"enabled": true
}
],
"overwrite_mode": "skip"
}'

Request Body:

FieldTypeRequiredDescription
policiesarrayYesArray of policies to import (max 100)
overwrite_modestringNoConflict handling: skip, overwrite, error (default: skip)

Response (200 OK):

{
"created": 2,
"updated": 0,
"skipped": 0,
"errors": []
}

GET /api/v1/policies/export

Export all policies for a tenant as JSON.

Request:

curl http://localhost:8081/api/v1/policies/export \
-H "X-Tenant-ID: my-tenant"

Response (200 OK):

{
"policies": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"name": "Rate Limit External APIs",
"description": "Limit external API calls per user per hour",
"type": "user",
"category": "dynamic-risk",
"tier": "tenant",
"conditions": [{"field": "user.role", "operator": "not_equals", "value": "admin"}],
"actions": [{"type": "block", "config": {"message": "..."}}],
"priority": 100,
"enabled": true,
"version": 4
}
],
"exported_at": "2025-01-02T10:00:00Z",
"tenant_id": "my-tenant"
}

Condition Reference

Available Fields

FieldTypeDescription
querystringThe user's query
responsestringThe LLM response
user.emailstringUser email
user.rolestringUser role
user.departmentstringUser department
user.tenant_idstringUser's tenant ID
risk_scorenumberCalculated risk score
request_typestringType of request
connectorstringMCP connector name
cost_estimatenumberEstimated cost in USD

Operators

OperatorDescriptionExample
equalsExact match{"field": "user.role", "operator": "equals", "value": "admin"}
not_equalsNot equal{"field": "user.role", "operator": "not_equals", "value": "admin"}
containsString contains{"field": "query", "operator": "contains", "value": "password"}
not_containsString doesn't contain{"field": "query", "operator": "not_contains", "value": "test"}
contains_anyContains any of values{"field": "query", "operator": "contains_any", "value": ["secret", "key"]}
regexRegular expression match{"field": "query", "operator": "regex", "value": "\\d{4}-\\d{4}"}
greater_thanNumeric greater than{"field": "cost_estimate", "operator": "greater_than", "value": 5.0}
less_thanNumeric less than{"field": "risk_score", "operator": "less_than", "value": 50}
inValue in list{"field": "user.role", "operator": "in", "value": ["admin", "superadmin"]}
not_inValue not in list{"field": "user.department", "operator": "not_in", "value": ["HR", "Legal"]}

Action Types

TypeDescriptionConfig
blockBlock the request{"message": "Request blocked"}
redactRedact sensitive content{"pattern": "\\d{4}-\\d{4}", "replacement": "[REDACTED]"}
alertSend alert notification{"channel": "slack", "message": "Alert!"}
logLog the event{"level": "warn", "include_query": true}
routeRoute to different provider{"provider": "anthropic"}
modify_riskAdjust risk score{"delta": 10}

Example Policies

Block non-admin MCP queries:

{
"name": "Block Non-Admin MCP",
"type": "user",
"conditions": [
{"field": "user.role", "operator": "not_equals", "value": "admin"},
{"field": "request_type", "operator": "equals", "value": "mcp_query"}
],
"actions": [
{"type": "block", "config": {"message": "Only admins can run MCP queries"}}
]
}

Alert on high-cost requests:

{
"name": "High Cost Alert",
"type": "cost",
"conditions": [
{"field": "cost_estimate", "operator": "greater_than", "value": 5.0}
],
"actions": [
{"type": "alert", "config": {"channel": "slack", "message": "High cost request: ${{cost_estimate}}"}}
]
}

Error Responses

HTTP StatusError CodeDescription
400INVALID_JSONInvalid JSON in request body
400VALIDATION_ERRORPolicy validation failed
401UNAUTHORIZEDMissing tenant ID
403TIER_LIMIT_EXCEEDEDPolicy limit exceeded for tier
404NOT_FOUNDPolicy does not exist
500INTERNAL_ERRORInternal server error

Next Steps