Runtime API
This is the normative HTTP binding of the OGR contract — the API a runtime (Policy Decision Point) exposes and an integration point (Policy Enforcement Point, PEP) calls.
There is no SDK layer. This API is the integration surface: one decision endpoint and one recipe. Every plugin this project ships is written against them, and a developer integrates their own agent by making the same call — the quickstart is the complete story.
All requests and responses are JSON, UTF-8, Content-Type: application/json.
Field names on the wire are snake_case, exactly as in the published JSON
Schemas. There is no protocol version on the wire: the runtime adapts to
the events it receives; a producer never version-gates.
Endpoints
| Endpoint | Purpose |
|---|---|
POST /v1/evaluate | The decision path — and the only event path: one GuardEvent in, one Verdict out. Every accepted evaluate also records the event |
POST /v1/heartbeat | Integration liveness ("agent idle" vs "integration went dark") |
GET /v1/health | Unauthenticated runtime liveness |
Two object pages document every wire field: the GuardEvent object and the Verdict object.
Removed in v0.8/v0.7: /v1/ingest (evaluate records every event it
judges — a second channel had nothing left to carry), /v1/enroll and
request signing (the org API key is the tenant boundary and identity floor),
and /v1/approvals (require_approval left the Verdict; a hold-and-ask
mechanism re-enters, if ever, as new design).
Base URL and mounting
Canonical endpoint paths are rooted at /v1/, served relative to a single
base URL. The base URL may include a deployment-specific prefix (the
reference runtime also mounts the same handlers under /api/public/ogr).
Clients must construct request URLs by joining a configured base URL with the
canonical /v1/... paths — and must not hard-code any other prefix:
base URL https://ogr.example.com → POST https://ogr.example.com/v1/evaluate
base URL https://host/api/public/ogr → POST https://host/api/public/ogr/v1/evaluate
Authentication
Every endpoint except /v1/health requires an organization API key:
Authorization: Bearer ogr_<key>
The key proves the ORGANIZATION — the tenant boundary every asserted name
(agent_id, agent_workspace) is resolved inside. WHERE an event lands is
the agent's business, not the key's: the workspace the agent was placed in
wins, then the workspace its agent_workspace names, and the key's own
default workspace is only the last resort for an agent asserting nothing.
A missing or invalid key produces 401 {"error": "unauthorized"}.
The key is also the identity floor: a caller whose four-tuple is all empty strings is still fully attributable — see the GuardEvent object.
The recipe
One recipe, normative — the same for a developer instrumenting their own agent loop and for a gateway proxying model traffic:
per model call:
1. mint step_id (fresh random id; binds this call's two events)
2. PRE-MODEL evaluate(step/request {step_id, four-tuple, llm_protocol,
payload: <raw request body>})
block → do not call the model
modifications.spans → apply in place BEFORE sending
no verdict → apply the configured fail mode (default: open)
3. call the model
4. POST-MODEL evaluate(step/response {same step_id, four-tuple, llm_protocol,
payload: <complete raw response body,
stream-reassembled if streamed,
+ timing>})
block → do not execute tool calls / do not release the held tail
modifications.spans → apply before the content is shown or acted on
no verdict → apply the configured fail mode
(tool RESULTS need no call of their own — they travel in the next
step/request and are judged there)
periodically:
5. heartbeat {integration, agent_id, counters}
Step 4 is the enforcement moment that matters most: the model's tool calls, held BEFORE execution, are the only copy of an action anyone can still refuse. For streamed responses, hold the tail and judge once — see the quickstart.
Errors
Error bodies are JSON with a stable error code:
| Status | Body | Meaning |
|---|---|---|
400 | {"error": "invalid_event", "details": [...]} | Body failed GuardEvent schema validation; details lists per-field issues |
400 | {"error": "invalid_body"} / endpoint-specific | Malformed request for non-event endpoints |
401 | {"error": "unauthorized"} | Missing or invalid API key |
429 | {"error": "rate_limited", "limit": n} | Rate limit exhausted |
5xx | — | Runtime failure; clients apply their configured fail mode |
Rate limits
A runtime rate-limits per API key; the reference default is 600
requests/minute in a fixed window. An exhausted limit produces
429 {"error": "rate_limited", "limit": 600}.
Back off on 429 — and treat a 429 on /v1/evaluate like an unreachable
runtime: apply your configured
fail mode (default: open;
closed is the explicit opt-in for gated categories).
Conformance
A runtime conforms if it serves all endpoints above with the stated
semantics, validates events against the published schemas, enforces the
authentication rules, assigns and returns event identifiers at ingress,
derives sessions, turns and steps server-side (re-attaching across context
compaction), pairs each step's two events by step_id, and never silently
drops an event it accepted.
An integration conforms if it implements the recipe in full, joins
configured base URLs with canonical paths, sends events with every field
present (empty-string assertions included), forwards raw bodies
undecomposed, reads identifiers from responses instead of minting them,
applies its configured fail mode on evaluate failure (default open,
configurable closed), applies modification spans before content proceeds,
honors unjudged when fail-closed, and judges streamed answers once, whole,
behind a held tail.
Machine-readable
Everything on these pages is also available in machine-readable form:
- OpenAPI 3.1 for the full Runtime API:
/api/docs/openapi.yaml - JSON Schemas (wire 0.8):
guard-event·verdict - Markdown: append
index.mdto any docs URL (e.g./api/docs/reference/evaluate/index.md), or fetch the whole corpus at/llms-full.txt