Integrating an agent platform with Zynth
This page answers the questions teams ask when putting Perihelium in front of an AI agent platform — an IDE agent, a coding CLI, an agent gateway. Each section says what to use and how.
If your question is not answered here, ask us and we will add it.
1. Logging a human in from a CLI
Two standard ways, both with PKCE (mandatory, S256-only) and a public client (the token
endpoint advertises none as an auth method):
- A loopback redirect on any port (RFC 8252 §7.3). Register
http://127.0.0.1/callback(orhttp://[::1]/callback) with no port. Your CLI binds whatever port the system gives it and presents it at/authorize; everything else in the URI is matched exactly. This is the one-browser case. - The device authorization grant (RFC 8628). For an SSH session, a container or a remote dev
box with no browser:
POST /api/v1/oauth/device_authorizationreturns a code and a Zynth page where the person approves, and your CLI polls the token endpoint.
The token endpoint accepts authorization_code, refresh_token, client_credentials and the
device grant. Full details, the polling answers and the limits:
Signing in from a CLI.
Agent onboarding is a different flow. It uses a handshake shaped like a device flow (park, poll, a human approves out of band) to open a tenant for an agent with no credential, and it returns an agent credential. It does not authenticate a human. See Agent-driven onboarding.
2. Knowing a token is still good
Zynth keeps the window in which a token can outlive a decision short and gives you ways to be exact:
| Mechanism | Detail |
|---|---|
| Token introspection (RFC 7662) | POST /api/v1/oauth/introspect answers now: active: false for a revoked grant, a person who no longer stands, a disabled client, and an agent that was killed or whose containment epoch is later than the token — even though that token still verifies offline. See Token introspection and revocation. |
| Short access tokens | 15 minutes by default. Verify locally against JWKS and re-check on expiry. |
| Refresh rotation with reuse detection | A replayed refresh token revokes the whole session. See tokens and sessions. |
| Revocation (RFC 7009) | POST /api/v1/oauth/revoke lets a client end its own grant: every token of it stops at once. |
| The check API | For a decision that must be current right now, ask Zynth rather than trusting a cached claim — see §5. |
| Agent kill switch | POST /api/v1/agents/{agent_id}/kill-switch contains an agent immediately and bumps a containment epoch, so already-minted tokens stop working instead of living out their 15 minutes. |
A resource server verifies offline for speed and introspects when the answer must be current: on a
short timer, or before a sensitive action. Your resource server authenticates to the introspection
endpoint as a client holding the authz:check scope — the same scope its calls to
/authz/service-check need.
3. Roles and groups in claims
Roles reach a plain OIDC login through the roles scope. A tenant administrator allows it on
the client (Organization → OAuth Clients → Role claims), and your application requests it
(scope=openid roles); the id_token and UserInfo then carry roles, the user's role codes in
the organization that signed in. There is no groups claim: an identity provider's groups are
mapped onto roles at SSO sign-in, so roles is the result. Above 64 codes the list is omitted,
not shortened and roles_truncated: true is sent. See
Role claims.
Roles and permissions also travel in a resource-bound access token. Request a token for one of
your registered resources (RFC 8707 resource) and it carries roles and permissions,
the latter filtered to that resource's namespace. Above 96 entries the list is omitted, not
shortened — the token carries permissions_truncated: true and no list, because a partial list
would be a fail-open authorization statement. Ask the check API when you see the flag.
Without the roles scope, the id_token and UserInfo carry identity only:
- id_token:
iss,sub,aud,azp,iat,exp,jti,sid,at_hashandtid, plus optionaltenant_slug,nonce,auth_time,amr,emailandemail_verified. - UserInfo:
sub,tid,tenant_slug, plusemailunder theemailscope and profile fields underprofile.
A role claim is true as of the moment the token was minted. For a decision that must be current, ask the check API. See also verifying tokens → resource claims.
tenant_slugis display-only and mutable. Never key authorization on it.tidis the stable tenant identifier.
4. Approving an action in your own system
Zynth runs approvals for the actions agents take through it:
- Agent action approvals — a denied tool call can return a pending
approval_request_id, and a human decides. See agent action approvals. - Actions your own system performs — a shell command in a sandbox, a push to a release branch, a
deploy. Your executor calls
POST /api/v1/authz/actwith the agent's token before it acts, and runs the action only onauthorized. See approvals for actions Zynth does not execute. - The approvals ledger —
GET /api/v1/approvals,POST /api/v1/approvals/{id}/decide. Agents can never decide their own approvals. - Access requests —
POST /api/v1/authz/requestsfor a principal asking for authority it lacks. See access requests.
5. Your own entitlements and budgets
Your own authorization model
You can define your own resources, permissions and roles in Zynth and ask it to decide:
- Resources — bound to a registered OAuth resource, supplying a namespace.
- Permissions —
<resource_ns>:<action>, with a description, a read-only flag and a risk weight. - Roles — your own roles, combining your permissions with platform ones.
Then ask:
POST /api/v1/authz/check— a principal asking about itself. Naming another principal answers 403.POST /api/v1/authz/service-check— your resource server, holding aclient_credentialstoken with scopeauthz:check, asking about any principal in its tenant.
Both take a checks array (maximum 50 per call) and return one result each. A check is a
question, not an act: it changes nothing and charges nothing.
You do not need to keep a parallel entitlements service for this. The full walkthrough —
registering a resource, defining permissions, the two ways to read a decision, and the one rule
that catches people out (allowed: true with a pending obligation is not "go") — is
Your own permissions.
A spend budget on an agent's task
When you mint a task-scoped agent token (POST /api/v1/agents/token — send a
DPoP proof with it; the software requires the proof
unless a deployment explicitly allows bearer tokens), you can pass a task with a
budget between 1 and 10,000.
A unit is one authorization decision that Zynth allows. It is not an LLM token, not a
dollar, and not a request your own system serves without asking Zynth. When the budget is spent,
further actions are refused with Task token budget of {budget} authorized actions is spent. It
fails closed: if the counter is unavailable, the request is refused rather than allowed.
Usage metering and billing
Zynth is the point where a decision is made, so it counts authorized actions as a containment limit on an agent's authority, and leaves metering of LLM tokens, compute and currency to the system that sees the usage. That keeps two things with different correctness requirements apart: a decision must be right now; a meter must be right eventually, and exactly.
The pattern that composes cleanly:
- Meter where the usage happens — your gateway or model proxy, or a metering service built for it (OpenMeter, Lago, Amberflo, Stripe Meters are all reasonable).
- Turn the meter's verdict into an authorization fact in Zynth — for example a
yourapp:llm:generatepermission you grant or withdraw, or a role your over-quota tenants lose. - Keep asking Zynth the question with
/authz/check. Your agents keep one enforcement point, and "over quota" denies exactly like any other authorization failure — same decision log, same reason, same audit trail.
6. Sending Zynth your agent events
POST /api/v1/telemetry/sdk accepts a security anomaly from your SDK or gateway and returns
202. It requires a caller with identity:self:write; the tenant comes from the token, never the
body.
| Field | Values |
|---|---|
kind | rate_limit_exceeded, csrf_rejected, input_rejected, auth_failure_burst, suspicious_request, prompt_injection_suspected, custom |
subject | free text, ≤ 1000 chars |
detail | free text, ≤ 5000 chars |
metadata | an object |
It is rate-capped per tenant and answers 429 beyond it. Reports land as sdk.anomaly_reported and
feed detection and the command centre.
7. The MCP gateway and standard clients
There are two MCP surfaces.
Operating Zynth over MCP
POST /api/v1/mcp (server name zynth-auth) is streamable HTTP, stateless: JSON-RPC 2.0
framing, plain JSON responses, no session id, no SSE. A standard MCP client that does not
require a session works. A denied tool call comes back as a tool result with isError, not a
JSON-RPC error. See operating Zynth over MCP.
The tool gateway
POST /api/v1/mcp/call/{server_name} brokers your agent's calls to your MCP servers,
authorizing each one. It relays a single JSON-RPC message and supports initialize, tools/list
and tools/call, with no session id and no SSE. It answers at the HTTP layer, so drive it from
client code that handles these statuses:
| Status | Meaning |
|---|---|
403 | Authorization denied — the body carries the reason and any pending approval_request_id. Also returned if the caller is not an agent principal; the gateway serves agents only. |
404 | Unknown server name |
429 | The gateway's own fail-closed budget, charged on every frame |
502 | Your upstream server failed |
200 | The upstream reply, with tools/list filtered to what the agent may call |
See MCP tool gateway.
8. Commercial questions
Pricing, self-hosting and managed regions are answered by the Zynth team directly — ask us. See also self-hosting.
Summary
| Question | Use |
|---|---|
| CLI login | A loopback redirect on any port (RFC 8252), or the device grant (RFC 8628) |
| Knowing a token is current | Token introspection (RFC 7662), 15-minute tokens, revocation (RFC 7009), refresh rotation with reuse detection, the check API, the kill switch |
| Roles and groups in claims | The roles scope on the id_token and UserInfo; resource-bound access tokens |
| Approvals | Agent action approvals, the approvals ledger, access requests |
| Your own authorization model | Resources, permissions and roles, decided by /authz/check |
| Per-task request budget | Task-scoped tokens with a budget of authorized requests |
| Metering LLM usage for billing | Meter in your gateway, feed the verdict back as a check |
| Event reporting | POST /api/v1/telemetry/sdk |
| Operating Zynth over MCP | POST /api/v1/mcp (stateless streamable HTTP) |
| Tool gateway | POST /api/v1/mcp/call/{server_name} |