Overrides API
Endpoints for managing session-scoped policy overrides. Implements the platform's override-semantics architecture decision (ADR-044).
Available on platform v7.1.0+. These endpoints are served by the Orchestrator and proxied through the Agent gateway on 8080; the gateway authenticates the caller (Basic auth) and sets the trusted X-Tenant-ID / X-Org-ID / X-User-Email identity headers the handlers require. Callers hitting the Orchestrator directly must set those headers themselves.
An override created through this API is a session "allow" override: for its TTL it bypasses the matched policy's deny for the creating user's tenant scope. Replacing a policy's terminal action (e.g. flipping block to require_approval) is a different, per-policy operation — see Per-policy action overrides below.
The session-scoped override endpoints on this page (/api/v1/overrides and the
/api/v1/policy-overrides alias) are not yet part of the published OpenAPI
specs, so this page remains their reference documentation. The closest
generated reference is the Agent API reference, which covers the
separate policy-scoped static override mechanism
(listStaticPolicyOverrides,
createStaticPolicyOverride).
POST /api/v1/overrides
Create a session-scoped override.
Request body
{
"policy_id": "pol-sqli-detector",
"policy_type": "static",
"override_reason": "Debugging prod incident INC-4521",
"tool_signature": "Bash",
"ttl_seconds": 900
}
| Field | Type | Required | Notes |
|---|---|---|---|
policy_id | string | yes | Policy to override. Accepts either the UUID or the human-readable identifier (the policy_id slug for static policies, the name for dynamic policies), so you can pass the policy_id from a block response's policy_matches[] straight through. |
policy_type | string | yes | static or dynamic. |
override_reason | string | yes | 1-500 chars. Free text. Mandatory per ADR-044. |
tool_signature | string | no | Scope override to a specific tool name. Omit for tool-agnostic scope. |
ttl_seconds | int | no | Requested TTL. Clamped server-side. Default 3600 (60m), hard cap 86400 (24h), min 60 (1m). |
Required identity headers (set automatically when the request comes through the Agent gateway): X-User-Email (or X-User-ID) — missing returns 401; X-Tenant-ID — missing returns 400. X-Org-ID is used as the row's organization scope when present.
The created override always records the session allow action — this endpoint does not accept an action_override or enabled_override field. Those belong to the per-policy override surface below.
Per-policy action overrides
Replacing a system policy's terminal action (or disabling it) for a whole tenant is a policy-management operation on the Agent's static-policy API, not this endpoint:
| Method | Path | Purpose |
|---|---|---|
POST | /api/v1/static-policies/{id}/override | Create an action/enabled override for a system policy (action_override, enabled_override, override_reason, optional expires_at). Enterprise license required (403 otherwise); duplicate override returns 409. |
GET | /api/v1/static-policies/{id}/override | Fetch the override for a policy. |
DELETE | /api/v1/static-policies/{id}/override | Remove the override. |
GET | /api/v1/static-policies/overrides | List per-policy overrides. |
Valid action_override values are the engine's terminal actions, canonically defined in platform/shared/policy.ValidOverrideActions:
| Action | Meaning when used as an override |
|---|---|
block | Deny the request (matches the policy's default). Most restrictive. |
require_approval | Route the request to the HITL approval queue. Queue operator controls (auto-approve delays, SLA escalation) are tier-gated; see HITL Approval Gates for the tier matrix. |
redact | Strip matched content from the request or response and proceed. |
warn | Allow the request and emit a warning log. |
log | Allow the request and record an audit entry. Least restrictive. |
Authoring-only actions (alert, route, modify_risk) are valid for defining a policy via POST /api/v1/policies but are rejected as override targets (invalid override action).
Response (201)
{
"id": "ov-f3a81c-...",
"policy_id": "pol-sqli-detector",
"policy_type": "static",
"expires_at": "2026-04-17T13:15:00Z",
"ttl_seconds": 900,
"requested_ttl": 900,
"created_at": "2026-04-17T13:00:00Z"
}
requested_ttl, clamped, and clamped_reason are omitempty — they appear only when a TTL was explicitly requested / actually clamped. When TTL is clamped:
{
"id": "ov-...",
"ttl_seconds": 86400,
"requested_ttl": 172800,
"clamped": true,
"clamped_reason": "exceeds_hard_cap"
}
clamped_reason is exceeds_hard_cap (requested above 86400s) or below_minimum (requested below 60s).
Error responses
| Status | Meaning |
|---|---|
| 400 | Invalid JSON body. Missing policy_id or override_reason, policy_type not static/dynamic, reason exceeds 500 chars. Or missing X-Tenant-ID header. |
| 401 | Missing caller identity (X-User-Email / X-User-ID header) |
| 403 | Policy is critical-risk (not overridable), or policy has allow_override=false |
| 404 | Policy not found |
| 500 | Policy lookup or insert failure |
Critical-risk rejection is enforced at the database level via trigger. allow_override=true on a critical-risk policy is coerced to false at insert time, so the server will always return 403 regardless of any race.
GET /api/v1/overrides
List active overrides scoped to the caller's tenant.
Query parameters
| Name | Type | Description |
|---|---|---|
policy_id | string | Filter to overrides for a specific policy. Accepts the UUID or the human-readable slug/name — the same identifier flexibility as create. |
include_revoked | bool | Include revoked overrides in results. Default false. |
Returns at most 100 overrides, newest first.
Response (200)
{
"overrides": [
{
"id": "ov-...",
"policy_id": "pol-sqli-detector",
"policy_type": "static",
"tenant_id": "tenant-x",
"override_reason": "Debugging",
"expires_at": "2026-04-17T13:15:00Z",
"created_at": "2026-04-17T13:00:00Z"
}
],
"count": 1
}
Requires the X-Tenant-ID header (set automatically by the agent); a missing header returns 400.
GET /api/v1/overrides/{id}
Fetch a single override by ID.
Response (200)
{
"id": "ov-...",
"policy_id": "pol-sqli-detector",
"policy_type": "static",
"tenant_id": "tenant-x",
"organization_id": "org-y",
"tool_signature": "Bash",
"override_reason": "Debugging",
"expires_at": "2026-04-17T13:15:00Z",
"created_at": "2026-04-17T13:00:00Z"
}
Null-valued optional fields (tool_signature, revoked_at, revoked_by, etc.) are omitted from the actual response rather than serialized as null. The lookup is scoped to the caller's tenant, so an override belonging to another tenant returns 404.
Error responses
| Status | Meaning |
|---|---|
| 400 | Missing X-Tenant-ID header |
| 404 | Override not found within the caller's tenant |
| 500 | Lookup failure |
DELETE /api/v1/overrides/{id}
Revoke an override. Next policy evaluation after the revocation does not consult it.
Response (200)
{
"id": "ov-...",
"revoked_at": "2026-04-17T13:10:00Z"
}
Error responses
| Status | Meaning |
|---|---|
| 400 | Missing X-Tenant-ID header (or blank override id) |
| 401 | Missing caller identity (X-User-Email / X-User-ID header) |
| 404 | Override not found within the caller's tenant, or already revoked |
| 500 | Lookup or update failure |
The lookup and the update are both tenant-scoped, so one tenant cannot revoke another tenant's override even with a known UUID. Emits an override_revoked audit event.
Audit events
Every override lifecycle event writes an entry to the audit log with its own request_type:
| Event | When |
|---|---|
override_created | Successful POST /api/v1/overrides |
override_used | Policy evaluation consumed an active override to flip a deny |
override_expired | Background cleanup detected TTL expiry |
override_revoked | Successful DELETE (user, admin, or policy-change-triggered) |
Lifecycle rows carry policy_decision: "override_lifecycle" — a recognized non-verdict marker — so they never appear in the verdict-centric decision feed (GET /api/v1/decisions) or inflate the per-action counts in POST /api/v1/audit/report.
Audit record policy_details includes: override_id, policy_ids, reason, ttl_seconds, requested_ttl, clamped, plus tool_signature when the override is tool-scoped, decision_id for override_used, and revoked_by for override_revoked. Search for all events tied to one override:
curl -X POST /api/v1/audit/search \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{"override_id": "ov-f3a81c-..."}'
Listing per-policy overrides through the canonical alias
Starting on platform v7.2.0, the agent exposes a canonical /api/v1/policy-overrides GET alias for the tenant's per-policy override list (the same list as GET /api/v1/static-policies/overrides, with an optional include_expired=true query param). It matches the existing policy-categories / static-policies / dynamic-policies naming pattern.
curl http://localhost:8080/api/v1/policy-overrides \
-H "Authorization: Basic $(echo -n 'client-id:client-secret' | base64)"
The response envelope is {"overrides": [...], "count": N}, where each row is a per-policy override record (action_override / enabled_override and scope fields) — not the session-override summary returned by GET /api/v1/overrides above. The alias exists because the Customer Portal's overrides handler proxies to the canonical path; callers using the canonical path against a pre-v7.2.0 agent get 404 page not found. Pre-v7.2.0 deployments can use /api/v1/static-policies/overrides for the same list.
See also
- Session Overrides concept
- Audit API with filters
decision_id,policy_name,override_id - Explainability to figure out whether an override is available before creating one
- HITL Approval Gates for the
require_approvalqueue semantics when using that action as an override
Operational Readiness Checklist
Before relying on this page in a production rollout, pair it with the core operations docs:
- Deployment Mode Matrix for self-hosted, Evaluation, Enterprise, SaaS, and In-VPC fit
- Failure Modes And Recovery for degraded-provider, connector, approval, and runtime behavior
- Capacity Planning for sizing and growth signals
- Community vs Evaluation vs Enterprise for limits, support surfaces, and upgrade triggers