For engineers integrating TenantSage

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.

REST API MCP SDK Webhooks

Authentication

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.

What you send
  • — A verified OIDC token from your IAM
  • — The activeScopeId the caller is currently operating in
  • — The action being requested and its purpose
  • — The payload (query, document reference, or candidate action)
What comes back
  • — A typed decision: ALLOW or a typed DENY
  • — The governed context, if allowed
  • — A retrieval / execution receipt reference
  • — The stage the decision was made at (S0–S12)

Illustrative shape — not a finalized schema

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"
}

Handle every one of these

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.

StageCodeMeaning
S0FAIL_UNAUTHENTICATEDIdentity, token, or scope failed to validate
S1FAIL_NO_AUTHORITYJoin-chain resolved no valid authority for this principal
S2FAIL_POLICY_BLOCKEDLegal hold, retention, or classification ceiling blocks it
S3FAIL_EMPTY_EEBThe sealed evidence boundary resolved to zero eligible sources
S4FAIL_NO_EVIDENCENothing inside the boundary matched the query
S5FAIL_CONTEXT_EXCEEDEDGoverned context exceeded allowed size or scope
S6FAIL_GENERATION_ERRORThe model failed to produce a usable candidate
S7FAIL_VALIDATIONGrounding, disclosure, or safety check failed
S8FAIL_APPROVAL_DENIEDHuman approver rejected the candidate action
S9FAIL_INTENT_REJECTEDExecution intent conflicted with an existing idempotency key
S10FAIL_EXECUTION_MISMATCHExecuted result didn't match the approved material hash
S11FAIL_RECEIPT_BUILDDurable receipt couldn't be constructed
S12FAIL_LEDGER_COMMITLedger write failed — output withheld even if everything upstream passed

Design rule for integrators

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.

Pilot access: the Gateway API, SDK, and MCP surface are being exercised with a small number of pilot integrations while security invariants are finalized. See the architecture page for current status.