Private betaRequest early access →

Sign in

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 (or http://[::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_authorization returns 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:

MechanismDetail
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 tokens15 minutes by default. Verify locally against JWKS and re-check on expiry.
Refresh rotation with reuse detectionA 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 APIFor a decision that must be current right now, ask Zynth rather than trusting a cached claim — see §5.
Agent kill switchPOST /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_hash and tid, plus optional tenant_slug, nonce, auth_time, amr, email and email_verified.
  • UserInfo: sub, tid, tenant_slug, plus email under the email scope and profile fields under profile.

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_slug is display-only and mutable. Never key authorization on it. tid is 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/act with the agent's token before it acts, and runs the action only on authorized. 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/requests for 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 a client_credentials token with scope authz: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:

  1. 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).
  2. Turn the meter's verdict into an authorization fact in Zynth — for example a yourapp:llm:generate permission you grant or withdraw, or a role your over-quota tenants lose.
  3. 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.

FieldValues
kindrate_limit_exceeded, csrf_rejected, input_rejected, auth_failure_burst, suspicious_request, prompt_injection_suspected, custom
subjectfree text, ≤ 1000 chars
detailfree text, ≤ 5000 chars
metadataan 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:

StatusMeaning
403Authorization 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.
404Unknown server name
429The gateway's own fail-closed budget, charged on every frame
502Your upstream server failed
200The 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

QuestionUse
CLI loginA loopback redirect on any port (RFC 8252), or the device grant (RFC 8628)
Knowing a token is currentToken introspection (RFC 7662), 15-minute tokens, revocation (RFC 7009), refresh rotation with reuse detection, the check API, the kill switch
Roles and groups in claimsThe roles scope on the id_token and UserInfo; resource-bound access tokens
ApprovalsAgent action approvals, the approvals ledger, access requests
Your own authorization modelResources, permissions and roles, decided by /authz/check
Per-task request budgetTask-scoped tokens with a budget of authorized requests
Metering LLM usage for billingMeter in your gateway, feed the verdict back as a check
Event reportingPOST /api/v1/telemetry/sdk
Operating Zynth over MCPPOST /api/v1/mcp (stateless streamable HTTP)
Tool gatewayPOST /api/v1/mcp/call/{server_name}