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.
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_by": "[email protected]",
"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
