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

EndpointPurpose
POST /v1/evaluateThe decision path — and the only event path: one GuardEvent in, one Verdict out. Every accepted evaluate also records the event
POST /v1/heartbeatIntegration liveness ("agent idle" vs "integration went dark")
GET /v1/healthUnauthenticated 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:

StatusBodyMeaning
400{"error": "invalid_event", "details": [...]}Body failed GuardEvent schema validation; details lists per-field issues
400{"error": "invalid_body"} / endpoint-specificMalformed request for non-event endpoints
401{"error": "unauthorized"}Missing or invalid API key
429{"error": "rate_limited", "limit": n}Rate limit exhausted
5xxRuntime 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: