Tenant Policy API
Use the tenant policy API to create per-tenant governance rules on /api/v1/dynamic-policies. These policies live on the Orchestrator and evaluate request-time context such as user attributes, estimated risk, cost, business rules, and media-governance conditions.
Overview
This is the public API path for what the platform historically called "dynamic policies." In public docs, the preferred term is tenant policies. The legacy endpoint name remains /api/v1/dynamic-policies.
Tenant policies differ from system policies in a few important ways:
| Feature | System Policies | Tenant Policies |
|---|---|---|
| API Endpoint | /api/v1/static-policies | /api/v1/dynamic-policies |
| Structure | Pattern-based rules | Conditions and actions |
| Location | Agent | Orchestrator |
| Use Case | PII, SQLi, redaction, fixed governance checks | Business logic, risk, cost, user and workflow controls |
| Scope | Request content | User, context, cost, risk |
Base URL:
http://localhost:8080
The Agent commonly proxies this to the Orchestrator. Direct Orchestrator access also works on 8081 if you expose it intentionally.
Authentication and tenant scope:
Authorization: Basic ...- Tenant context (derived from Basic auth credentials) preferred
X-Org-IDstill accepted for backward compatibility on this handlerX-User-IDrecommended on mutating requests for audit attribution
Verified Routes
| Method | Path | Purpose |
|---|---|---|
GET | /api/v1/dynamic-policies | List tenant policies |
POST | /api/v1/dynamic-policies | Create a tenant policy |
POST | /api/v1/dynamic-policies/import | Import policies |
GET | /api/v1/dynamic-policies/export | Export policies |
GET | /api/v1/dynamic-policies/effective | Get effective policies for a tenant |
GET | /api/v1/dynamic-policies/{id} | Get a single policy |
PUT | /api/v1/dynamic-policies/{id} | Update a policy |
DELETE | /api/v1/dynamic-policies/{id} | Delete a policy |
POST | /api/v1/dynamic-policies/{id}/test | Test a policy |
GET | /api/v1/dynamic-policies/{id}/versions | List policy versions |
Listing and Filtering
Verified query parameters handled by the list endpoint:
| Query param | Notes |
|---|---|
type | Free-form type filter used by the service |
category | Must start with dynamic- or media- |
search | Search term |
sort_by | Sort field |
sort_dir | Sort direction |
page | Page number, default 1 |
limit | Preferred page size, max 100 |
page_size | Deprecated alias for backward compatibility |
enabled | true or false |
The category behavior matters for real deployments:
- use
dynamic-*for general tenant governance - use
media-*for media governance and analyzer-driven rules
Example:
curl "http://localhost:8080/api/v1/dynamic-policies?category=dynamic-risk&enabled=true&limit=20" \
-H "Authorization: Basic $(echo -n 'client-id:client-secret' | base64)" \
List responses return a policies array plus pagination. Each policy resource includes:
| Field | Notes |
|---|---|
id | Policy ID |
name | Display name |
description | Optional description |
type | content, user, risk, cost, or other supported policy types |
category | Must use a valid dynamic-* or media-* prefix |
tier | system, organization, or tenant |
conditions | Condition list |
actions | Action list |
priority | Evaluation priority |
enabled | Whether the policy is active |
version | Current version |
tenant_id | Tenant scope |
organization_id | Present for organization-tier policies |
tags | Optional tags |
created_at, updated_at | Timestamps |
created_by, updated_by | Audit attribution |
deleted_at | Present on soft-deleted records when included |
Creating a Tenant Policy
The create handler requires a category and validates that it begins with dynamic- or media-.
Minimal example:
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": "High-cost research requests",
"type": "cost",
"category": "dynamic-cost",
"conditions": [
{
"field": "cost_estimate",
"operator": "greater_than",
"value": 5
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Request exceeds the tenant budget threshold"
}
}
]
}'
Successful creates return 201 Created with a policy object. Validation failures return a structured error with code VALIDATION_ERROR.
Example response:
{
"policy": {
"id": "550e8400-e29b-41d4-a716-446655440002",
"name": "High-cost research requests",
"type": "cost",
"category": "dynamic-cost",
"tier": "tenant",
"conditions": [
{
"field": "cost_estimate",
"operator": "greater_than",
"value": 5
}
],
"actions": [
{
"type": "block",
"config": {
"message": "Request exceeds the tenant budget threshold"
}
}
],
"priority": 0,
"enabled": true,
"version": 1,
"tenant_id": "tenant-123",
"created_by": "[email protected]",
"created_at": "2026-03-29T12:00:00Z",
"updated_at": "2026-03-29T12:00:00Z"
}
}
Test, Export, Import, and Version History
These are the routes teams use once tenant policy programs become operational rather than experimental:
| Method | Path | Why it matters |
|---|---|---|
POST | /api/v1/dynamic-policies/{id}/test | Validate a rule before enabling it |
GET | /api/v1/dynamic-policies/export | Export tenant policies for review or migration |
POST | /api/v1/dynamic-policies/import | Import policy sets with overwrite control |
GET | /api/v1/dynamic-policies/{id}/versions | Inspect policy history |
GET | /api/v1/dynamic-policies/effective | See the effective tenant policy set |
Verified helper payload shapes:
{
"matched": true,
"blocked": true,
"actions": [
{
"type": "block",
"config": {
"message": "Request exceeds the tenant budget threshold"
}
}
],
"explanation": "The policy matched cost_estimate > 5",
"eval_time_ms": 0.42
}
{
"versions": [
{
"version": 1,
"snapshot": {
"name": "High-cost research requests"
},
"changed_by": "[email protected]",
"changed_at": "2026-03-29T12:00:00Z",
"change_type": "created",
"change_summary": "Initial policy creation"
}
]
}
What To Watch For
- The public docs use tenant policy terminology, but the path remains
/api/v1/dynamic-policies. - Category validation is strict. If you use an unsupported category prefix, the handler rejects the request.
- For production governance programs, tenant policies are where you usually encode business-specific risk rules, escalation rules, cost thresholds, and media-governance conditions that go beyond built-in system policy coverage.
Platform v7.2.0 Changes
Three updates to the policy surface shipped in platform v7.2.0. They are additive; existing tenant-policy callers keep working unchanged.
context_aware is a valid type
Three seeded system policies (Tenant Isolation, Debug Mode Restriction, Sensitive Data Control) ship with type=context_aware. Before v7.2.0, PUT /api/v1/policies/{id} rejected the update payload with VALIDATION_ERROR because the type allowlist omitted that value, so the Portal's Edit-policy flow 400'd for all three policies. The canonical allowlist is content, user, risk, cost, context_aware, media, rate-limit, budget, time-access, role-access, mcp, connector. No SDK changes required; the SDKs do not send type on update.
Legacy snake-case policy IDs accepted by per-policy endpoints
Per-policy endpoints (GET / PUT / DELETE / POST /test / GET /versions) previously rejected seeded policy IDs like sensitive_data_control and tenant_isolation with Invalid policy ID format because the validator only accepted UUIDs and the sys_* prefix. The validator now also accepts the legacy snake-case form. UUID and sys_* forms continue to work.
GET /api/v1/policies honours tier and category
The unified cross-tier listing endpoint at /api/v1/policies (used primarily by the Customer Portal) now honours tier (system, organization, tenant) and category (security-*, pii-*, compliance-*, etc.) query params at the handler boundary. The repo supported these params before v7.2.0 but the handler dropped them, so every Tier / Category dropdown in the Portal's Policies page returned the full unfiltered list.
curl "http://localhost:8080/api/v1/policies?tier=system&category=security-sqli" \
-H "Authorization: Basic $(echo -n 'client-id:client-secret' | base64)"
