Skip to main content

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
}
FieldTypeRequiredNotes
policy_idstringyesPolicy 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_typestringyesstatic or dynamic.
override_reasonstringyes1-500 chars. Free text. Mandatory per ADR-044.
tool_signaturestringnoScope override to a specific tool name. Omit for tool-agnostic scope.
ttl_secondsintnoRequested 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:

MethodPathPurpose
POST/api/v1/static-policies/{id}/overrideCreate 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}/overrideFetch the override for a policy.
DELETE/api/v1/static-policies/{id}/overrideRemove the override.
GET/api/v1/static-policies/overridesList per-policy overrides.

Valid action_override values are the engine's terminal actions, canonically defined in platform/shared/policy.ValidOverrideActions:

ActionMeaning when used as an override
blockDeny the request (matches the policy's default). Most restrictive.
require_approvalRoute 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.
redactStrip matched content from the request or response and proceed.
warnAllow the request and emit a warning log.
logAllow 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

StatusMeaning
400Invalid JSON body. Missing policy_id or override_reason, policy_type not static/dynamic, reason exceeds 500 chars. Or missing X-Tenant-ID header.
401Missing caller identity (X-User-Email / X-User-ID header)
403Policy is critical-risk (not overridable), or policy has allow_override=false
404Policy not found
500Policy 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

NameTypeDescription
policy_idstringFilter to overrides for a specific policy. Accepts the UUID or the human-readable slug/name — the same identifier flexibility as create.
include_revokedboolInclude 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

StatusMeaning
400Missing X-Tenant-ID header
404Override not found within the caller's tenant
500Lookup 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

StatusMeaning
400Missing X-Tenant-ID header (or blank override id)
401Missing caller identity (X-User-Email / X-User-ID header)
404Override not found within the caller's tenant, or already revoked
500Lookup 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:

EventWhen
override_createdSuccessful POST /api/v1/overrides
override_usedPolicy evaluation consumed an active override to flip a deny
override_expiredBackground cleanup detected TTL expiry
override_revokedSuccessful 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

Operational Readiness Checklist

Before relying on this page in a production rollout, pair it with the core operations docs: