Skip to main content
When evaluate returns anything other than allow, the response carries a deny_code (short, stable machine string) and a deny_reason (human-readable detail). Codes are stable across versions so dashboards, alerting, and routing logic can pin to them. The same codes appear on decision: hold and decision: escalate — the code names why the engine reached that state, not whether the action was outright denied. See the Glossary for the four-value decision enum.

Engine codes (from evaluate)

These are returned by the rule engine itself.

Actor & role

deny
The actor’s ID is on the policy’s deny list. Decision: deny. Operator action: confirm whether the block is intended; if the actor should be reinstated, remove them from the deny list.
escalate
The actor’s ID is on the policy’s escalate list — every action by this actor routes to a human reviewer. Decision: escalate. Operator action: review and resolve the queued item.
escalate
The actor’s role is on the policy’s escalate list. Decision: escalate. Operator action: same as ACTOR_ESCALATED but the trigger is the role, not the individual identity.
hold
The actor’s ID is on the policy’s hold list — actions wait for manual release. Decision: hold. Operator action: review and release in the held queue.
hold
The actor’s role is on the policy’s hold list. Decision: hold. Same handling as ACTOR_HELD but the trigger is the role.
deny
The action class requires a verified actor identity (requires_verified_actor = true) but the request carried no valid, in-binding actor_identity.v1 assertion — so the request fails closed before authority evaluation. Decision: deny. A self-asserted actor_id is not a trusted signal; this is a missing-credential error, not a policy denial. Integrator action: attach a top-level actor_identity assertion signed by a trusted issuer, with subject.principal_id equal to actor_id and its binding matching the request’s action_type, tenant, and environment.

Approvals

deny
The action requires N approvals; M were supplied. Decision: deny. deny_reason carries the exact counts. Operator action: collect the additional approvals and resubmit.
deny
The action requires N approvals from a specific role (e.g. controller); M matching approvals were supplied. Decision: deny. deny_reason names the role and the counts.

Risk

escalate
The computed risk score met or exceeded the policy’s escalate threshold. Decision: escalate. deny_reason carries the score and threshold.
hold
The computed risk score met or exceeded the policy’s hold threshold. Decision: hold. Operator action: review the evaluated context and release or deny.

Rate limits and windows

deny
A user-defined rate counter exceeded its configured maximum inside the configured window. Decision: deny by default (a policy may override to hold or escalate). deny_reason carries the counter name and the n/m ratio.
deny
The action fell inside an active freeze window (e.g. quarter-end close). Decision: deny. deny_reason names the window. Operator action: wait for the window to close, or grant an override if policy permits.

Policy resolution

deny
No policy rule matched the request and fail_mode is closed (the default). Decision: deny. Operator action: confirm whether a matching rule should exist; if so, publish it.
deny
No active constraint bundle is published for this org and fail_mode is closed. Decision: deny. Operator action: publish a bundle for the affected action class.
deny
A rule matched but its template’s condition set could not match any branch. Decision: deny. Operator action: review the template — usually indicates a constraint expects a context field the caller did not provide.
deny
The action class has requires_state_snapshot = true (the default for every action class) but the evaluate request did not include a top-level state_snapshot. Decision: deny, returned with HTTP 200 (a normal deny body, not a 4xx error). Caller action: add a top-level state_snapshot to the request body — it is not nested under context:
The GitHub Action injects this automatically, so you only hit this when calling /v1-evaluate directly. SDK callers should branch on the response body’s deny_code, not on the HTTP status. Retry advice: retry with the field added — this is a recoverable, input-shape error, not a policy denial.
hold
Two or more rules share the same layer, priority, and created_at — the engine refuses to choose between them. Decision: hold (fail-safe — the action is not denied, but it is not authorized either). Operator action: republish one of the rules with a higher priority or a different layer to break the tie.

Deploy gate codes (from deployment.production)

These codes are emitted by the Production Change Protection V1 policy, which runs as a pre-check on every deployment.production evaluation before the bundle rules engine. They appear on the deny_code field alongside the normal decision values.
deny
The active deploy gate policy rule explicitly denied the request. Common causes: permit.action_type does not match deployment.production; an override block is present but override_reason or authority_basis is missing. Decision: deny. Retry advice: never — fix the input.
deny
The actor’s role is not on the approved list for deployment.production. Approved roles: admin, approver, deploy_engineer. Decision: deny. Operator action: verify the actor’s role in actorMetadata.role and re-submit, or request a role change.
deny
The permit’s environment field does not match context.environment, or context.environment is absent. Decision: deny. Operator action: ensure the permit was issued for the environment you are deploying to and that context.environment is set.
deny
No permit was provided, the permit is missing permit_id, or permit.verified is not true. Decision: deny. Operator action: call POST /v1-verify-permit with the permit token before submitting the deploy evaluation; pass the result back as context.permit.
deny
The permit’s issued_at timestamp is older than the deploy gate’s 15-minute TTL (900 s), or issued_at is absent. This shorter TTL applies to the deploy gate only; the default permit TTL is 1 hour. Decision: deny. Operator action: re-issue a fresh permit and retry. Do not reuse permit tokens across deploy attempts.
escalate
An unknown or inconsistent state was detected — for example, a malformed ISO-8601 timestamp in evaluation_confirmed_at or permit.issued_at, or evaluation_confirmed is absent. Decision: escalate. A human reviewer must inspect the context and authorise or deny before the deploy proceeds. This code is emitted even in pilot (shadow) mode because an inconsistent state means the evaluation itself cannot be trusted.
allow
A valid override was applied — both context.override.override_reason and context.override.authority_basis were present and non-empty. Decision: allow. The override short-circuits all other deploy gate rules. The deny_reason field carries the approver ID and reason for audit purposes. Treat override-approved evaluations as high-priority audit events.

API error codes (HTTP errors, not decisions)

These are not deny_code values on a decision — they are error_code values on a non-2xx HTTP response. They surface the same way in client code: branch on the code, not the message.
error
Missing or invalid API key. HTTP 401. Check the Authorization header and the key’s status in the console.
error
Concurrent request rate exceeded. HTTP 429. Retry with exponential backoff. Distinct from RATE_LIMIT_EXCEEDED (which is a decision, not a transport error).
error
Per-tier monthly evaluation quota exhausted. HTTP 429 with plan, limit, and used fields in the body. Operator action: upgrade the plan or wait until the next billing cycle.
error
More than 1000 items in a /v1/evaluate:batch request. HTTP 400. Split the batch.

How to handle these in your client

Pin to the codes, not to deny_reason strings. The reasons are human-readable and may change without notice; the codes are the contract.

Stability

Codes in this page are stable across v1. New codes may be added; existing codes are not renamed or removed without a deprecation notice.