One control plane. Two places an agent acts.
An agent calls a model, and it calls tools. Those are different protocols with different failure modes, and Permisyn governs both from one passport — which is also what makes the third thing on this page possible.
Refused at the middle step and the provider is never contacted, so there is nothing to pay for.
Refused at the middle step and the real server never sees the call. If the authorize cannot complete, nothing runs.
A tool call is matched to the model decision that asked for it, so a receipt names the prompt behind an execution — and a call no governed model asked for can be refused.
Needs both halves: an agent whose model traffic does not pass through Permisyn records no decisions to match. How the match is made and recorded.
- 01One passport, both surfaces
- The allow-list, delegation grants, the freezes and the kill switch are the same objects on either side. An agent governed for its model calls is not separately configured for its tool calls.
- 02How strong the match was, on the receipt
- Recorded as the rung it actually reached — same agent and same arguments at the top, the tool name alone at the bottom. Matching is org-scoped by necessity, since wrapping names an agent per MCP server and the two surfaces' names for one workload legitimately differ. A decision authorises exactly one execution.
- 03The chain appears on its own
- A matched tool call takes the model call's chain and names that run as its parent, so prompt-to-tool-call custody shows up on the public verifier with nothing configured — no chain id to thread through your own processes.
- 04Off until you decide otherwise
- Observe records the binding and never refuses, which is where to start. Require refuses a tool call no governed model asked for — and is itself refused for a workspace that has recorded no decisions in the last day, rather than switching on and denying everything.
The gate
Seventeen checks and three enforcement modes, in the order a call meets them.
What runs before your provider sees the request.
Seventeen checks, broadest scope first, so the code you get back names the thing that actually stopped you. Allowed or refused, the call leaves a signed receipt — a denial is evidence too, not just an error.
- 01Plan rate limit429 plan_rate_limited
- 02Monthly run quota402 plan_limit_reached
- 03Header profile requires a user identity422 profile_user_required
- 04Org-wide freeze503 ai_frozen
- 05Team halt503 team_frozen
- 06User halt503 user_frozen
- 07Upstream URL and vault key resolution400 / 401
- 08Per-plan agent limit402 plan_limit_reached
- 09Agent kill switch503 agent_killed
- 10Passport time window403 passport_time_restricted
- 11Passport expiry403 passport_expired
- 12Model and provider allow-list403 passport_violation
- 13Region and data residency403 passport_region_violation
- 14Delegation grant, revoke-and-lock403 delegation_violation
- 15Action scope on the declared tools403 action_prevented
- 16Cost cap402 cost_cap_exceeded
- 17Chain budget402 chain_budget_exceeded
- 18 — AFTER THE PROVIDER
The tool the response actually asked for is checked once more, which necessarily happens after the provider has been paid. On a stream the check runs frame by frame, so the call is withheld rather than merely recorded — but the money is gone either way. Gate 15 exists to close that gap, before any of it moves.
Most gateways govern which model. This governs which tool.
An agent that may call GPT-4 is not thereby allowed to call refund_customer. Four modes, and the difference between them is where in the request they act — which decides whether you are billed for the attempt.
The call runs and the violation is written onto the signed receipt. Nothing is stopped. Enforcement being off by default is deliberate: turning it on should be a decision you make, not one made for you by an install.
A tool call outside the passport is stripped from the response before your code sees it. The provider has already run and already billed you. Three violations inside ten minutes halt the agent, using the same mechanism as a human pulling the kill switch.
A request that declares a tool outside the passport is refused before your provider is contacted, so there is nothing to pay for. What a model improvises mid-response is still a response-side strip — declaring a tool and calling one are different events, and only the first can be caught early.
The same request-side gate, for the caller who declares a whole tool catalogue on every call and would rather not have the whole call refused over one entry. Only the disallowed tools are removed from the request; the allowed ones go upstream and are billed. The removed names are on the signed receipt, so what was taken out is part of the record rather than a silent edit.
tools/call leaves verifies the same way. That surface has a page of its own — its own ten gates, its own install, its own limits.Proof
Signatures anyone can check without us, and authority that can only narrow as it is handed on.
Proof that does not require trusting us
Every decision is signed with your org's own Ed25519 key. Verification needs your public key and nothing else — no Permisyn account, no API call to us, and it keeps working if we are down or gone.
- 01What is signed
- Run id, agent, team, timestamps, status, tokens, cost, provider, model, the authorization decision and the path that produced it, the passport digest and revision. Optionally a hash of the completion.
- 02What is not
- Prompts and completions. Receipts are metadata by default; the trade is spelled out below rather than buried.
- 03Offline verifier
- Download verify_receipt.py and check a receipt, a chain, or a whole passport history from the public key alone. For a partner who has to make that check inside their own service rather than by hand, the same verifier is published as a package — permisyn-verify on PyPI, @permisyn/verify on npm.
- 04Proof on the response
- Ask for it and a governed response carries its own signed token in an X-Permisyn-Receipt header, so the code that received the answer can confirm it was governed in its own process — no callback, no account. A summary that says so: it names its own scope, and the fetched receipt stays the authority on cost and decision path. Streamed responses answer deferred, since the headers leave before the completion exists.
- 05Transparency log
- Receipts are committed to an append-only Merkle log with signed tree heads and per-leaf inclusion proofs. Heads carry an Ed25519 signature under a published log key, so a head you pinned is one we cannot later disown.
- 06Proof it was never rewritten
- An inclusion proof shows an entry is in today's tree. It cannot see that yesterday's tree was a different tree — a log rebuilt overnight passes it. So pin a head, and later ask for a consistency proof that the head you pinned is still inside the current one, unchanged. Your monitor folds the hashes itself and fails loudly the first time they do not reconcile.
- 07Anchored to Bitcoin
- Roots are stamped via OpenTimestamps, so not even we can backdate history. Confirmation takes hours — a fresh anchor reads pending until a mined block carries it.
- 08Tamper detection
- Verification compares the stored row against the signed bytes and reports which fields differ, so an erased field is caught rather than returning a bare false.
Authority that can only narrow as it is passed on
When one agent hands work to another, the delegate's permissions are the intersection of what it was given and what the giver actually had. A chain cannot gain authority by getting longer.
- 01Chain of custody
- Hops are hash-linked through a chain id and a parent run id, and the whole chain verifies as one object.
- 02The tool boundary, proved
- A model hop reading a tool's output is the one seam a hash link cannot close on its own — the prompt and the tool result are not the same measurement. Declare the output you consumed and that hop verifies too. A match proves the caller held exactly those bytes; we never see them, only the hash.
- 03Runtime delegations
- An agent mints a narrowed grant for another at runtime. Grants are revocable, and a delegate that drifts has its own grants revoked automatically.
- 04Portable agent credentials
- A short-lived, audience-bound pass an agent hands to a partner API or an MCP server you do not run, naming exactly what it may do. They check it with your public key — no Permisyn account, no call to us. It can only ever say less than the agent's real passport, never more. It proves the scope was authorized when issued and has not expired, not that it’s still authorized this second, which is what the minutes-long expiry is there to bound.
- 05Cross-org visas
- Admit another company's signed agent into your org on a key you pinned. What it gets is the intersection of its passport, your accepted terms, and its sponsor's own authority.
- 06Chain budgets
- A spend ceiling for a whole chain, not just per call, enforced at gate 17.
Running it
What violations cost, where the telemetry lands, what an auditor gets, and how it plugs in.
What your policy violations actually cost
Most tools show you spend. This one separates the spend that broke your own rules, prices it, and tells you what enforcement saved — including the counterfactual for calls that were refused before a provider could bill you.
- 01Advisory violation
- You paid, and the call broke a rule you had chosen not to enforce yet.
- 02Blocked after spend
- You paid the provider and the tool call was stripped before your code saw it.
- 03Unenforceable stream
- A streamed violation nothing could hold — the class Permisyn kept for its own limit. Narrow now that streams are enforced frame by frame, and left in place because the rows already filed under it must not change meaning.
- 04Prevented pre-flight
- Refused at gate 15. Priced at input tokens only, because that is what it would have cost.
- 05Caps that stop, not chart
- Per-agent, per-chain, and per-request ceilings enforced at request time.
- 06Rolled up by human
- Spend and governance coverage by user, team and agent, exportable as CSV.
Into the stack you already watch
A span per governed call in your own collector, carrying the decision and the rule behind a refusal. Permisyn's own latency is reported separately from your provider's, and a refused call is never marked an error — a rule doing its job is not a fault, and counting it as one would spike your error rate every time enforcement worked.
- 01OTLP over HTTP
- Datadog, Honeycomb, Grafana or a plain collector. One URL in settings, no SDK, and the spans nest inside the trace your app already produces.
- 02Signed webhooks
- 19 event types including gate.blocked, agent.killed, agent.action_violation and shadow_agent.detected.
- 03Alert channels
- Slack, Microsoft Teams, PagerDuty, Jira, Linear, email, or a generic webhook.
- 04In-app notifications
- Fleet events land in the dashboard without wiring anything up first.
- 05Live wall
- A websocket view of allow and block decisions as they happen.
- 06Latency you can audit
- Every proxied response carries X-Permisyn-OverheadMs, denials included.
Readiness assessed from traffic, not a questionnaire
Six frameworks scored against your real signed log rather than against what someone typed into a form, and exported as an attestation signed with your own key.
- 01Frameworks
- EU AI Act, SOC 2, HIPAA, NIST AI RMF, RBI ML, and SOX. Which are available depends on plan, and the product says which rather than implying all.
- 02Signed attestation
- Generated on demand from the enforcement log, not authored by hand.
- 03AI Bill of Materials
- Which models, providers and agents are in use, and under whose authority.
- 04Auditor portal
- A scoped, token-gated view for a regulator or external auditor. They see the evidence without an account and without your dashboard.
- 05Never-authorized attestation
- The answer to “was this agent ever allowed to do X in that window?”, replayed from the witnessed history and signed — with its own proof strength attached, so an unknown dimension answers indeterminate instead of a convenient no.
- 06Evidence bundles
- PDF certificates and ZIP bundles for a run, a chain, or a window.
One environment variable, no wrapper SDK.
Permisyn speaks the OpenAI protocol, so your existing client keeps working. Provider keys move into an encrypted vault and stop living in application code and run logs.
POST /v1/chat/completions
POST /v1/messages
POST /v1/completions
GET /v1/models
# authorize without forwarding, for a
# sidecar in your own VPC
POST /api/control/authorizeAuthenticate with a combined key, or send your provider key as normal and add X-Permisyn-Key alongside it.
What it does not do.
Every item below is a real edge in the code today. You would find them in a trial regardless; you should not have to.
- 01No SSO or SAML yet
- Sign-in is email and password, Google, or GitHub. Role-based access control exists — viewer, developer, admin — but directory integration does not, and no amount of Enterprise positioning changes that.
- 02No failover between providers
- One call resolves to one upstream. If your provider is down, Permisyn does not silently reroute you to a different one, because a governed call that quietly changes model is a worse outcome than a failed call you can see.
- 03Streaming enforcement reaches only what we can parse
- A disallowed tool call is withheld mid-stream — the frames carrying it are held until the name is checked and dropped if the passport refuses. Three things it does not reach: the tokens, which were already billed; the model's own prose, which is not a tool call and is not ours to edit; and a response shape our SSE reader does not recognise, where the receipt says action_scope_unverified rather than implying a check happened.
- 04Region enforcement trusts a table
- Residency is checked against a maintained provider-to-region table plus Azure hostname matching. It is a policy control, not a network proof — nothing here inspects where a packet physically went.
- 05Azure and custom upstreams limit response checks
- For those two providers the response-side action check can be unverified, and when it is, the receipt says so with action_scope_unverified and the response carries a warning header rather than implying a check that did not happen.
- 06Prompts are not stored by default
- Receipts are metadata. That is a privacy decision, and the trade is real: a receipt proves what was decided and what it cost, not what was said. Opt in per call with an output hash if you need the completion bound too.
- 07The token on the response is a summary
- Ask for proof on the response and you get a signed token stating the run, agent, model, decision, enforcement mode, passport digest and a hash of the answer. Not cost, not token counts, not the decision path: those are computed after the response has left, and a second implementation of them is a second chance for two signed statements about one call to disagree. The token names its own scope and points at the full receipt.
