One gateway. Every governed request looks the same.
Whatever calls TenantSage — an app, an AI assistant, an agent, or an existing RAG pipeline — sends the same shape of request and gets back the same shape of decision. This page is conceptual and illustrative: the public API surface is still being finalized during the pilot phase.
You bring the identity. We don't replace your IAM.
TenantSage consumes a verified identity from your existing OIDC/JWKS-based IAM and resolves it into an authority snapshot for this request. It never issues its own primary identities.
- — A verified OIDC token from your IAM
- — The
activeScopeIdthe caller is currently operating in - — The action being requested and its purpose
- — The payload (query, document reference, or candidate action)
- — A typed decision:
ALLOWor a typedDENY - — The governed context, if allowed
- — A retrieval / execution receipt reference
- — The stage the decision was made at (S0–S12)
What a governed request conceptually looks like.
POST /v1/govern/retrieve
Authorization: Bearer <oidc_token>
{
"activeScopeId": "scope_hotel_2291",
"action": "retrieve",
"purpose": "incident_summary",
"query": "room incident reports, last 7 days"
}
// Response — ALLOW
{
"decision": "ALLOW",
"stage": "S12",
"eebHash": "sha256:8f2a…",
"receiptId": "rcpt_9c11…",
"context": [ "…governed evidence chunks…" ]
}
// Response — DENY
{
"decision": "DENY",
"stage": "S2",
"reason": "FAIL_POLICY_BLOCKED",
"detail": "legal hold active on requested scope"
}
Typed denial codes, by stage.
Every denial names the stage it happened at and why. Clients shouldn't treat a denial as a generic error — each code implies a different retry, escalation, or user-facing message.
| Stage | Code | Meaning |
|---|---|---|
| S0 | FAIL_UNAUTHENTICATED | Identity, token, or scope failed to validate |
| S1 | FAIL_NO_AUTHORITY | Join-chain resolved no valid authority for this principal |
| S2 | FAIL_POLICY_BLOCKED | Legal hold, retention, or classification ceiling blocks it |
| S3 | FAIL_EMPTY_EEB | The sealed evidence boundary resolved to zero eligible sources |
| S4 | FAIL_NO_EVIDENCE | Nothing inside the boundary matched the query |
| S5 | FAIL_CONTEXT_EXCEEDED | Governed context exceeded allowed size or scope |
| S6 | FAIL_GENERATION_ERROR | The model failed to produce a usable candidate |
| S7 | FAIL_VALIDATION | Grounding, disclosure, or safety check failed |
| S8 | FAIL_APPROVAL_DENIED | Human approver rejected the candidate action |
| S9 | FAIL_INTENT_REJECTED | Execution intent conflicted with an existing idempotency key |
| S10 | FAIL_EXECUTION_MISMATCH | Executed result didn't match the approved material hash |
| S11 | FAIL_RECEIPT_BUILD | Durable receipt couldn't be constructed |
| S12 | FAIL_LEDGER_COMMIT | Ledger write failed — output withheld even if everything upstream passed |
Fail closed. Always.
If your integration can't reach TenantSage, or gets an ambiguous response, the correct behavior is to deny the request — not to fall back to answering without governance. That's the same rule TenantSage applies to itself internally.