Identity Exchange with Clerk Organizations
This guide connects a Clerk application that uses Organizations to Runtype Identity Exchange. When you finish, each request carries a verified end user (the Clerk user) and a verified tenant (the Clerk organization), and an agent can isolate data by organization and pass the organization to your tools.
For how Identity Exchange works with any OpenID Connect (OIDC) provider, see End-user identity and credentials.
Prerequisites
You need the following:
- A Clerk application with Organizations enabled.
- A Runtype API key with the
INTEGRATIONS:WRITEpermission.
Create a Clerk JWT template
Runtype requires every integration to name a specific audience, and it rejects a token whose aud claim does not match. A default Clerk session token has no aud claim, so create a JWT template that adds one.
To create the template, complete the following steps:
-
In the Clerk Dashboard, open the JWT templates page and create a blank template named
runtype. -
Set the template claims to the following JSON:
Clerk JWT template claimsReplace
YOUR_RUNTYPE_AUDIENCEwith a name for the Runtype relying party, such asruntype-storefront. Use the same value asaudiencewhen you register the integration. -
Set the token lifetime to 60 seconds. Runtype rejects a token issued more than 10 minutes ago, with 30 seconds of clock skew allowed.
Clerk sets the azp claim to the origin of the page that requested the token. You list those origins in authorizedParties when you register the integration.
Register the integration
Register one integration for your Clerk instance. Runtype selects the integration by the token’s verified issuer, so create only one active integration for each Clerk instance.
The following request registers a Clerk integration:
Replace the following placeholders:
rt_live_YOUR_API_KEY: your live Runtype API key.YOUR_CLERK_FRONTEND_API: the host of your Clerk Frontend API URL, for exampleyour-app.clerk.accounts.devfor a development instance.YOUR_RUNTYPE_AUDIENCE: theaudvalue from your JWT template.https://shop.example.com: every origin that requests tokens for Runtype.
The response contains the integration id, which starts with idint_. Runtype uses this ID as the isolation namespace for identities from this integration.
The following table explains the Clerk-specific settings:
claimMap paths use dot notation, so a claim nested in an object, such as o.id, also works.
Send a proof on each request
Mint a fresh token for every request, and send it as identityProof. Runtype verifies the proof on each request and does not store it. Set provider to clerk. If you send a provider value, it must match the provider of the integration that the issuer selects, or Runtype rejects the proof with provider_hint_mismatch.
In the browser, request a token from the JWT template:
Send the token as a bearer token to your backend. Never send the Clerk __session cookie to Runtype.
Your backend attaches the token to a dispatch request:
Replace agent_YOUR_AGENT_ID with the saved agent ID and YOUR_CLERK_TOKEN with the token from getToken. Asynchronous dispatch (Prefer: respond-async) does not accept identityProof and returns 400 ASYNC_IDENTITY_PROOF_UNSUPPORTED.
For an embedded chat widget, pass the same object as identityProof to POST /v1/client/init and POST /v1/client/chat. The Persona widget (version 4.24.0 or later) does this for you when you set identityProvider and getIdentityProof:
The widget calls getIdentityProof before every chat request and sends the token as identityProof: { provider: 'clerk', token }. If the function returns null, the widget stops the turn. Without identityProvider, the widget sends the proof only when it starts a session, so an agent with the tenant-isolated Tenancy Strategy rejects its chat requests with tenant_scope_required. For widget setup and conversation history, see Conversation history in an embedded widget.
Scope the agent to the organization
A verified proof sets _tenant and _endUser for the execution. To isolate data by Clerk organization, save the agent with the tenant-isolated Tenancy Strategy:
To send the organization to your API or MCP server, reference {{_tenant.id}} in a tool header. Runtype fills it from the verified proof, and the model cannot set it. To let your API verify the identity itself, send {{_identity.token}} instead. For the rules, see Tool template variables. To choose between organization and per-user isolation, see Tenancy strategies.
Verify the setup
Check that Runtype admitted the proof:
- Widget. The
POST /v1/client/initresponse carriesvisitor.identityStatus. The valueadmittedmeans that Runtype verified the proof.not_providedmeans that the request had no proof.ignoredmeans that the request had a proof but Identity Exchange admission is not enabled for your organization. - Conversation history routes. The
X-History-Identity-Statusresponse header carries the same values. - Tools. A tool that sends
{{_tenant.id}}receives the Clerk organization ID, such asorg_2abc.
Troubleshooting
Runtype returns 401 with error: invalid_identity_proof when it rejects a proof. The errorDescription field names the reason:
Runtype returns 403 with error: Tenancy lock-down when a proof verifies but the agent’s Tenancy Strategy rejects the scope:
tenant_scope_required: the token has noorg_id. The user has no active organization, or the integration has noclaimMap.tenant.tenant_assurance_below_floor: the tenant is not verified. This happens when the request carries a tenant but no admitted proof.
Next steps
- Tenancy strategies: choose what Runtype isolates by organization or user
- Tool template variables: send the verified organization to your API or MCP server
- End-user identity and credentials: register other identity providers and connect end-user accounts