Skip to content

API reference

Base URL: https://api.inpolicy.ai/api/v1/agent Authentication: Authorization: Bearer inp_live_… on every request. Rate limit: 120 req/min per key by default; configurable per key.

All Agent Governance endpoints are mounted under /api/v1/agent. The tenant is resolved from the API key. You never pass a tenant id in a request body.

Status Meaning
400 Bad request (validation error). Response body has details.
401 Missing / invalid / revoked API key.
403 Key lacks required scope OR feature gate disabled on the key.
404 Resource not found (e.g. unknown sessionId on GET /conversation).
429 Rate limit exceeded. Retry after window_seconds.
5xx Upstream error. Retryable with exponential backoff.

POST /api/v1/agent/governance/conversation/turn

Section titled “POST /api/v1/agent/governance/conversation/turn”

The primary surface. Record one turn in a conversation and get the updated policy state.

Request

{
"sessionId": "sess_abc",
"turn": {
"role": "user",
"content": "I'm 16 — can I buy this?"
},
"recentContext": ["prior turn content"],
"priorHistory": null,
"endUserContext": {
"attributes": { "customer_tier": "basic", "region": "EU" }
},
"policyAreaIds": ["data-handling"],
"tokenBudget": 600,
"idempotencyKey": "turn-7a9c-...",
"checkOutput": false,
"ttlSeconds": null
}
Field Required Description
sessionId ✅ Stable caller-chosen conversation id.
turn.role ✅ user | assistant | tool. system is rejected.
turn.content ✅ The turn’s text content.
recentContext — Last 2–3 prior turns for disambiguation. Not stored.
priorHistory — Bootstrap history; only used on the first call per session.
endUserContext.attributes — Opaque attributes for policy matching. Not stored.
policyAreaIds — Scope retrieval to specific policy areas.
tokenBudget — Approx tokens for injectionBlock. Default 600. Range [50, 4000].
idempotencyKey — Replays within 5 min return the cached response.
checkOutput — Only valid when turn.role === "assistant". Requires post_inference_checking: "allowed" on the key.
ttlSeconds — Override conversation TTL. Range [300, 604800].

Response (200)

{
"sessionId": "sess_abc",
"turnIndex": 3,
"diff": {
"newlyApplicable": [ /* ApplicablePolicy[] */ ],
"newlyReleased": [],
"stillActive": [ /* ApplicablePolicy[] */ ],
"bindingEvents": [
{
"turnIndex": 3,
"policyId": "pol_123",
"event": "binding_started",
"ts": "2026-04-24T12:00:00Z"
}
]
},
"activePolicies": [ /* ApplicablePolicy[] */ ],
"injectionBlock": "## Applicable Company Policies\n- [Policy 4.2]...",
"tokenEstimate": 184,
"postInferenceCheck": null,
"traceId": null
}

ApplicablePolicy:

{
"id": "pol_123",
"title": "No customer PII in external AI tools",
"summary": "Mask customer emails, names, account IDs...",
"severity": 4,
"enforcement": "fix" | "warning" | "audit",
"citationRef": "Policy 4.2",
"tags": ["pii"]
}

Latency: p50 ~100 ms, p95 ~500 ms regardless of input size (pre-inference is bounded by top-K retrieval, not prompt length). See Latency expectations.


GET /api/v1/agent/governance/conversation/:sessionId

Section titled “GET /api/v1/agent/governance/conversation/:sessionId”

Read-only snapshot of conversation state. No mutation, no TTL reset.

Response (200)

{
"sessionId": "sess_abc",
"turnCount": 7,
"activePolicies": [ /* ApplicablePolicy[] */ ],
"injectionBlock": "...",
"bindingLog": [ /* BindingEvent[] */ ],
"updatedAt": "2026-04-24T12:00:00Z"
}

Returns 404 if the session has been evicted (past TTL) or never existed.


DELETE /api/v1/agent/governance/conversation/:sessionId

Section titled “DELETE /api/v1/agent/governance/conversation/:sessionId”

End a conversation and purge its Redis state. Idempotent. Returns 204.


POST /api/v1/agent/governance/check-tool-call

Section titled “POST /api/v1/agent/governance/check-tool-call”

Gate a proposed agent tool call against the session’s active policies. Requires tool_call_governance: "allowed" on the API key; returns 403 otherwise.

Request

{
"sessionId": "sess_abc",
"toolName": "send_email",
"arguments": { "to": "competitor@acme.com", "subject": "Q3 roadmap" },
"intent": "proposed"
}

Response (200)

{
"allow": false,
"riskScore": 0.91,
"violations": [
{
"policyId": "pol_conf_01",
"matchedText": "competitor@acme.com",
"explanation": "Recipient domain matches listed competitor.",
"severity": 4,
"confidence": 0.95,
"enforcementType": "fix"
}
],
"suggestedArguments": null,
"traceId": null
}

allow: false happens when at least one violation has enforcementType: "fix" AND confidence ≥ 0.7. Any other violation (warning, audit, or low-confidence) returns allow: true with the violations surfaced for audit.


POST /api/v1/agent/governance/pre-inference

Section titled “POST /api/v1/agent/governance/pre-inference”

Legacy single-shot surface. Returns applicable policies + injection block for one prompt.

Prefer POST /conversation/turn for multi-turn agents. Same shape plus incremental diff.

Request

{
"sessionId": "sess_abc",
"prompt": "Draft a reply to our Acme account exec",
"recentMessages": [],
"context": { "surface": "email" },
"endUserContext": null,
"policyAreaIds": null,
"tokenBudget": 600,
"isUpdate": false
}

Response (200)

{
"sessionId": "sess_abc",
"cacheHit": false,
"applicablePolicies": [ /* ApplicablePolicy[] */ ],
"injectionBlock": "## Applicable Company Policies\n- [Policy 4.2]...",
"rationaleRequired": false,
"tokenEstimate": 184,
"traceId": null
}

Despite the “same shape” framing above, this is not identical to conversation/turn’s response: the policy list here is applicablePolicies, not activePolicies, there’s no diff or turnIndex field, and two fields appear that conversation/turn doesn’t return — cacheHit (whether this result was served from the idempotency cache) and rationaleRequired (whether the caller must supply a rationale before proceeding, per the API key’s settings).


POST /api/v1/agent/governance/post-inference

Section titled “POST /api/v1/agent/governance/post-inference”

Check a model output for policy violations. Requires post_inference_checking: "allowed" on the API key.

Request

{
"output": "Sure, here's the refund for John Smith (john@acme.com)...",
"sessionId": "sess_abc",
"prompt": "Draft a refund email",
"surface": "email",
"policyAreaIds": null,
"traceId": "trc_from_pre_inference"
}

Response (200)

{
"safe": false,
"riskScore": 0.72,
"violations": [
{
"policyId": "pol_pii_01",
"matchedText": "john@acme.com",
"explanation": "Contains unredacted customer email address.",
"severity": 4,
"confidence": 0.88,
"enforcementType": "fix",
"enforcementText": "[REDACTED]"
}
],
"suggestedRedaction": "Sure, here's the refund for John Smith ([REDACTED])...",
"policyCitations": ["pol_pii_01"],
"traceId": "trc_..."
}

POST /api/v1/agent/governance/check-output/stream

Section titled “POST /api/v1/agent/governance/check-output/stream”

Incremental check on accumulated output as the LLM streams. Requires post_inference_checking: "allowed" on the API key. Caller posts repeatedly with the full text so far; the server detects on the new portion plus a small overlap. See Streaming output checks for the full walkthrough.

Request

{
"sessionId": "sess_abc",
"fullText": "Sure, here is the refund for John Smith (john@acme.com)",
"lastCheckedOffset": 0,
"isFinal": false
}

Response (200)

{
"sessionId": "sess_abc",
"newViolations": [
{
"policyId": "policy_pii",
"matchedText": "John Smith (john@acme.com)",
"explanation": "Customer name and email...",
"severity": 4.5,
"confidence": 0.92,
"enforcementType": "fix",
"enforcementText": "[REDACTED]",
"offset": 28
}
],
"watermark": 56,
"isComplete": false,
"riskScore": 0.92,
"traceId": "trc_…"
}

Pass watermark back as lastCheckedOffset on the next call. Set isFinal: true on the last chunk to force detection on any tail content below the chunk threshold.


POST /api/v1/agent/governance/check-output/finalize

Section titled “POST /api/v1/agent/governance/check-output/finalize”

End-of-stream verdict for the full output — same response shape as POST /post-inference. Requires post_inference_checking: "allowed" on the API key.

Request — same shape as check-output/stream, with isFinal: true.

Response (200) — same shape as POST /post-inference above (safe, riskScore, violations, suggestedRedaction, policyCitations, traceId).


Every key has a set of scopes. The defaults are enough for the full API.

Scope Grants
governance:read record_turn, pre-inference, conversation state GET/DELETE
governance:check check_output, check_tool_call, post-inference
policies:read future: listing policies
policies:ack future: recording policy acknowledgements
feedback:write future: submitting thumbs-up/down feedback

Set via the InPolicy dashboard on the API key. Default: all off.

Setting Values Effect
post_inference_checking "disabled" | "allowed" Unlocks check_output + checkOutput: true on record_turn
tool_call_governance "disabled" | "allowed" Unlocks check-tool-call
conversation_ttl_seconds number | null Override default 4h TTL. Range [300, 604800].