Introspection & Gateway Gating
Validate tvsess_ tokens from your own MCP gateway and enforce Token Vault schedules with one HTTP call.
POST /api/mcp-oauth/introspect is an RFC 7662-shaped introspection endpoint built
for one job: letting your own MCP gateway accept tvsess_ tokens as its auth,
so Token Vault becomes the identity + policy plane for services you host yourself.
Bearer-as-subject
There is no separate caller credential. The token being validated is the
Authorization header (the OIDC UserInfo pattern) — a caller can only introspect
tokens it already holds:
curl -X POST https://api.tokenvault.uk/api/mcp-oauth/introspect \
-H 'Authorization: Bearer tvsess_…'{
"active": true,
"sub": "firebase-uid",
"email": "owner@example.com",
"agent_id": "agent-doc-id",
"agent_name": "My Agent",
"agent_description": "Does things",
"exp": 1751234567,
"authorization": { "allowed": true, "reason": null, "retry_after": null }
}{ "active": false }Gating a gateway in ~20 lines
Any service fronted by your gateway can honor Token Vault agents, kill switches, and schedules with a single upstream call:
import time, requests
def check(bearer: str) -> tuple[bool, str | None]:
r = requests.post(
"https://api.tokenvault.uk/api/mcp-oauth/introspect",
headers={"Authorization": f"Bearer {bearer}"},
timeout=5,
)
body = r.json()
if not body.get("active"):
return False, "invalid or revoked token"
authz = body.get("authorization") or {}
if not authz.get("allowed"):
return False, authz.get("reason") or "blocked by policy"
return True, NoneThe authorization block
The response carries the verdict of Token Vault's ambient policy evaluation —
time_window rules attached to the agent — so your gateway enforces schedules
without implementing any policy engine:
| Field | Type | Meaning |
|---|---|---|
allowed | bool | No ambient policy denies access right now |
reason | string | null | Human-readable denial reason |
retry_after | int | null | Seconds until the window next opens |
Only time_window rules are evaluated ambiently. Request-scoped rules
(ip_allowlist, geo_restrict) would see your gateway's IP rather than the
agent's; stateful rules (rate_limit, max_usage) must not fire on a passive
check; manual_approval must not page a human on every poll. Those stay enforced
on Token Vault's own surfaces.
Fail-closed: if policy evaluation errors, the response is still active: true
but authorization.allowed: false with Cache-Control: no-store — the token is
valid, the gate is provisionally shut, and the next call re-evaluates.
Caching
Responses tell you exactly how long a verdict may be reused:
| Case | Cache-Control |
|---|---|
active: false | no-store — never cache a negative |
Valid, no time_window policy | private, max-age=min(60, ttl) |
Valid, time_window present | private, max-age=min(15, ttl) — schedule edits propagate in ≤ 15 s |
| Valid, ambient eval error | no-store |
Success responses carry Vary: Authorization: the bearer is the subject, so a
cache must key by token, never by URL.
Errors
| HTTP | When |
|---|---|
| 401 | Missing or malformed Authorization: Bearer header |
| 429 | Rate limited — honor Retry-After |
Ready to try it?
Sign up free with Google — your credentials stay on your own webhook, and the quickstart gets an agent fetching its first credential in about ten minutes.