Prerequisites
- A Runlayer instance (referred to as
RUNLAYER_URLbelow) - An agent account with Client ID and Client Secret (created in Settings → Agent Accounts)
- For OBO recipes: at least one active delegation from a user to the agent account
curlandjqinstalled (for shell examples)
Authentication Methods
This cookbook uses three different authentication methods depending on the endpoint:
User API keys are the easiest option for scripting and automation. Create one in Settings → Personal API keys in the Runlayer UI. They work anywhere a user JWT works — delegations, session grants, and proxy calls — but they always carry user identity, not agent identity.
API keys cannot be used for agent account authentication (
/oauth/token with client_credentials). That endpoint requires the agent’s Client ID and Client Secret. Use API keys for management endpoints and for proxy calls where user-only identity is sufficient.End-to-End OBO Flow
The full On-Behalf-Of lifecycle, from one-time user consent through a proxied tool call. Each numbered step maps to a recipe below.Get an M2M Token (Autonomous Agent)
Use this when your agent operates independently, without a specific user context.1
Request an M2M token
2
Call an MCP tool
3
Handle token expiry
M2M tokens are valid for 1 hour. Re-request a token before it expires. A simple approach is to request a fresh token before each batch of calls, or cache the token and refresh when you receive a
401.Get an OBO Token with a Runlayer User UUID
Use this when your system already has the Runlayer user UUID (for example, stored when the delegation was created). This is the simplest OBO flow.1
Obtain the Runlayer user UUID
If you do not already have the UUID, list delegations and store each Store the
delegator_user_id in your own user table when you can identify which app user created that delegation. The list is useful for populating or refreshing your mapping, but it is not an email directory.delegator_user_id alongside your own user identifier for future lookups.The delegations API returns
delegator_user_id (a UUID) but does not include the user’s email. If your starting point is an email address, either use the email-based OBO flow below or store email -> delegator_user_id in your own database when the delegation is created.2
Request an OBO token
Getting an OBO token is a two-step flow: mint an agent token with Python equivalent:
client_credentials (the same call as M2M above), then exchange it for an OBO token via RFC 8693 token exchange. The agent token is cacheable and reused across many user exchanges, and your client_secret never travels on the exchange call.Pass
resource=[f"{RUNLAYER_URL}/api/v1/proxy/<server-id>/mcp"] to scope
the minted token to specific servers. The token’s aud is narrowed to the
servers the principal can access (dual-form: resource URL +
runlayer:server:<id> URN). This is currently observe-only — recorded but
not yet enforced at the proxy.3
Call MCP tools on behalf of the user
Get an OBO Token with a User Email
Use this when your system tracks users by email rather than UUID. Runlayer looks up the active user whoseemail matches exactly (case-sensitive) and issues an OBO token scoped to them.
Get an OBO Token with a WorkOS Access Token
Use this when the end user authenticates through WorkOS/AuthKit and your app holds their access token. No user UUID mapping needed — Runlayer resolves the user server-side. Getting an OBO token is a two-step flow: mint an agent token withclient_credentials, then exchange it for an OBO token via RFC 8693 token exchange. Per RFC 8693 §2.1 the user identity goes in subject_token and the agent JWT goes in actor_token. As with the other recipes, the agent token is cacheable and reused across many user exchanges, and your client_secret never travels on the exchange call:
Mapping External User IDs to Runlayer Users
Most applications maintain their own user identity. To issue OBO tokens, you need to map your user IDs to Runlayer user UUIDs. Choose the approach that fits your architecture:Option A: Store UUID at delegation time
When a user delegates to your agent in the Runlayer UI, persist theirdelegator_user_id in your database alongside your own user record. At OBO token time, look up the stored UUID — no extra API calls needed.
Option B: Query the Delegations API
Use this when your app has already stored a Runlayer user UUID and you want to confirm that UUID still has an active delegation. The delegations list can also refresh your local mapping, but it cannot tell you which UUID belongs toalice@example.com because the response does not include email addresses.
The delegations response includes
delegator_user_id, is_active, and timing fields such as starts_at, expires_at, and revoked_at. It does not include user email addresses. If your app starts from email, use Option C or keep your own email -> delegator_user_id mapping.Option C: Pass the user email
Skip UUID mapping: pass the user’s email assubject_token with subject_token_type=urn:runlayer:token-type:user-email. Runlayer matches the email exactly (case-sensitive) against the User.email record and resolves the user server-side. Prefer UUIDs for long-lived integrations — emails can change.
Option D: Use a WorkOS access token directly
Skip the mapping entirely. If your users authenticate through WorkOS/AuthKit, pass their JWT assubject_token with subject_token_type=urn:ietf:params:oauth:token-type:access_token. Runlayer resolves the user server-side from the JWT claims.
Whichever option you choose, the token exchange itself is the same — see the End-to-End OBO Flow diagram above.
Session Grants for OAuth-Protected Servers
When an agent makes OBO calls to an OAuth-protected MCP server (e.g., GitHub, Slack, Google Workspace), it needs session grants — a user’s OAuth credentials shared with the agent for that specific server. Without a session grant, proxy calls return401: No credentials available for this server.
Session grants are independent from delegations. A delegation controls who the agent can act as. A session grant controls whose OAuth credentials are used for a specific server.
Create a personal session grant
The grantor must have an active OAuth session for the server (they must have connected to it first).id, grantor_user_id, is_active, and timestamps.
List session grants
Toggle shared / personal
This endpoint flips the current state of your grant: personal becomes shared, shared becomes personal. It does not set an absolute value — theshared field in the request body is ignored. It only operates on your own grant, and requires agent-account manage access (shared grants are an admin-level concern).
Credential resolution order
When an agent makes an OBO call to an OAuth-protected server, Runlayer resolves credentials in this order:Revoke a session grant
Revoking a session grant does not revoke the user’s delegation. The agent can still issue OBO tokens for that user, but calls to the affected OAuth server will fail until a new session grant is created.
Example: Slack agent with shared fallback
A “Support Bot” agent account needs to call a Slack MCP server on behalf of multiple users.1
Alice creates a personal grant
Alice has authorized Slack in Runlayer. She creates a personal session grant (using her JWT or API key):
2
Alice promotes her grant to shared
Only the grantor can toggle their own grant, and toggling requires agent-account manage access. Alice (an admin) promotes it so users without their own Slack credentials can still use the bot:
3
OBO calls resolve credentials automatically
- Support Bot as Alice -> uses Alice’s own OAuth credentials (her grant is matched first because she is both grantor and caller)
- Support Bot as Bob (Bob has no grant) -> falls back to Alice’s shared grant
- Support Bot as Carol (Carol creates her own personal grant later) -> uses Carol’s own credentials