Gateway Mode

Pre-check request before LLM call

Gateway Mode Step 1: Call this endpoint before making your own LLM API call.

The Agent validates the request against policies and returns:

  • verdict - the canonical allow | deny, the same vocabulary POST /api/v1/decide uses. Read this one. Since v11 the pre-check never holds a request: a require_approval policy is a deny.
  • approved: true if the request is allowed (retained)
  • decision_id — the decision identifier. Use it for the subsequent audit call, AND for GET /api/v1/decisions/{decision_id}/explain, which is keyed on it.
  • context_id — a deprecated alias of decision_id, same value
  • Optional approved_data from MCP connectors
  • Rate limit information

approved_data is only prefetched for clean approvals (#2868): when the request is blocked, requires HITL approval, or requires redaction, connector prefetch is skipped and approved_data is never populated — governed data is not fetched for a request that may not proceed.

Context expires after 5 minutes.

Example Flow

1. SDK calls pre-check → gets context_id, approved=true
2. SDK makes direct LLM call (OpenAI, Anthropic, etc.)
3. SDK calls audit with context_id and response metadata
post/api/policy/pre-check

Request

Headers

Authorizationstring required
Example:Basic bXktb3JnOkFYT04tVjIteHh4

OAuth2-style Basic authentication header. Format: Basic base64(clientId:clientSecret)

  • clientId: Your organization identifier (required)
  • clientSecret: Authentication credential (optional for community mode)

Not required when DEPLOYMENT_MODE=community.

X-Axonflow-PEP-Handshakestring

base64url (padding optional) of the PEPHandshake document.

The ADR-065 PEP capability handshake: base64url of a compact JSON document in which an external enforcement point declares what it is and which obligations it can discharge. See PEPHandshake for the document.

Absent is the default and changes nothing. A caller that omits the header takes byte-for-byte the path it took before this header existed.

What an absent header means for a redaction depends on the plane, by design (PRD v11 section 1 item 16, #4257). The MCP passes discharge a redaction of the content they hand back: the request pass (check-input, check_policy) masks the statement, and a redaction that masks nothing in the statement is refused unsupported_obligation to every caller, and one that masks a request parameter to every caller that has not declared field_redact at version 2 (on Community, to every caller) (#4264); the response passes (check-output, the MCP server's check_output) mask the rows or the message. /api/v1/decide and the gateway pre-check return a decision rather than content, so a required redaction is a field_redact obligation for the enforcement point, and a caller that has not declared field_redact is refused unsupported_obligation. A caller that declares NO redaction (capabilities: []) is refused on each of these planes; on Community the MCP passes still return a checksum validator's masked content to it (reachable only through a directly inserted detection_action_overrides row) until #4122.

A header that is PRESENT and cannot be read is refused, never treated as absent: degrading a malformed declaration to "legacy caller" would go on handing an enforcement point obligations it had just said it cannot discharge. The refusal is 400 and its message names this header, which matters on /api/v1/access/evaluation where the refusal is rendered through that surface's existing incomplete_evaluation code and the message is the only thing distinguishing a malformed HEADER from a malformed body ENVELOPE.

Present more than once is refused: RFC 7230 permits an intermediary to join repeated field lines with a comma, and a comma is outside the base64 alphabet, so a joined pair can only decode to malformed. That is why the document is base64 rather than raw JSON, which would join into something a lenient parser might accept.

When a decision carries a mandatory obligation the declared set does not cover, the request is answered 200 with verdict: deny and the reason unsupported_obligation (ADR-065 invariant 8) - a decision about the request, not a transport error. That holds on every edition. An Enterprise deployment adds a second reason beginning pep_capability_unsupported naming the gap, and refuses a checksum validator's mask on the MCP passes; a Community deployment hands that masked content over (#4257 split 2, #4122).

Request body

querystring required

Query to validate

user_tokenstring

JWT token for user authentication

client_idstring required

Client application ID

data_sourcesstring[]

MCP connectors to fetch data from

contextobject

Additional context

Response

Pre-check result

decision_idstring

The decision identifier, and the CANONICAL name for it. Every other plane that mints a decision calls it decision_id: POST /api/v1/decide, POST /api/v1/mcp/check-input, POST /api/v1/mcp/check-output, and the AuthZEN adapter's context.decision_id.

IT IS THE KEY TO ANOTHER ENDPOINT, and that linkage was previously documented nowhere:

  • GET /api/v1/decisions/{decision_id}/explain returns the full policy explanation for this decision.

A caller that reads only context_id below still has the value, but nothing told it that the value works on that endpoint, so integrations built against this plane silently lost a capability that integrations built against /api/v1/decide got for free.

POST /api/v1/overrides was the second such endpoint until v11.0.0. It is keyed on a policy, never on decision_id, and from v11.0.0 it writes nothing and answers 409 LEGACY_POLICY_WRITE_FROZEN (#4252).

Also valid for the subsequent POST /api/audit/llm-call for five minutes, which is what context_id was originally named for.

verdict'allow' | 'deny'

The CANONICAL answer to "may I do this?", in the same vocabulary and with the same values POST /api/v1/decide returns.

Read this rather than approved in new integrations. Across the governed surface the same question was answered by five different keys in two different types - verdict (string) on /api/v1/decide, approved (bool) here, allowed (bool) on both MCP check endpoints, decision (bool) on the AuthZEN adapter and decision (string) on the decisions feed - so a client typed from one plane could not deserialise another.

Since v11 the pre-check never holds a request: a require_approval policy is a deny, because an anchored CHALLENGE is a refusal. So verdict and approved never disagree.

The AuthZEN adapter (POST /api/v1/access/evaluation) keeps its boolean decision and is not a divergence to be fixed: AuthZEN 1.0 mandates a boolean, and the four-valued state rides in that endpoint's response context behind profile negotiation.

approvedboolean

Whether the request is allowed. RETAINED and not deprecated - every shipped SDK reads it. Prefer verdict.

context_idstring

DEPRECATED ALIAS of decision_id, carrying the identical value.

Retained because every shipped SDK reads it and removing it would break them all. New integrations should read decision_id; this member will be removed no earlier than the release after the one that introduced decision_id.

approved_dataobject

Data fetched from MCP connectors

policiesstring[]

Policies that were evaluated.

segment_resolution_failed no longer appears here (it did from #3312). No segment gate stands on the pre-check any more: it resolved the caller's governance segments and refused the request when that failed, on behalf of an organization's segment-scoped static rows. Those rows no longer decide: the anchored engine authors this verdict and reads no segments (PRD v11 §1.1, §1.2).

expires_atstring date-time

When the context expires

block_reasonstring

Reason if request was blocked

trace_idstring

W3C OpenTelemetry trace_id (32-char lowercase hex) emitted by the decision tracer. Optional: present when the tracer is enabled via AXONFLOW_OTEL_ENDPOINT, omitted otherwise. Policy Enforcement Points propagate this id downstream so multi-gateway decisions stitch into one end-to-end trace.

engine'anchored'

Which policy engine authored this verdict: the ADR-065 decision plane, the only author on this route (PRD v11 §1.1). Omitted on a refusal no engine decided - an authentication failure, or a request refused before the policy pass ran.

subject_typestring

The type of principal the verdict was decided for (PRD v11 §1.6): User for a verified user token, Client when the request presented no user identity and its client credential is the principal. Omitted wherever engine is.

policy_bundlestring

The digest of the policy set that decided: the system corpus's restriction for this route and the organization root - the organization's active typed document composed with the deployment's baseline permission pack, or, while it has published nothing, the implicit bundle of that pack and the organization template. A rollback reinstates an earlier digest. Omitted wherever engine is.

Example response

{
  "trace_id": "b3a1f1f3a8c6e0d791bc3e7a8c2d5f4a"
}

Changes

No recorded changes to this operation across all 1 revision of this API.