Cross-App Access is in Beta and must be enabled for your Serval organization before the Settings → Security → Cross-App Access controls appear. If you don’t see that section, ask your Serval representative to turn it on. The admin UI and OIN listing may still change.
Your Serval host
Every URL in this guide is relative to your Serval deployment. Substitute your own host wherever you seehttps://<your-serval-api-host>:
The audience, token endpoint, and the API you call all use this one host. The issuer is the exception — it’s your Okta org URL (the
iss claim on the ID-JAG), not a Serval host. Serval hardcodes no host in the exchange, so use the host for your org’s region throughout.
What you’ll need
Have these on hand before you start. The last three are produced during setup.Prerequisites
- An Okta org with Cross-App Access and AI Agents enabled. Both features must be available on your org.
- Okta admin permissions to configure OIN applications, machine assignments, and AI agents.
- A Serval administrator account: the Admin organization role in the Serval org you’re configuring.
- Cross-App Access enabled on your Serval org: this Beta feature is off by default. If Settings → Security → Cross-App Access isn’t present, ask your Serval representative to enable it.
- The Serval app added from the OIN: add Serval to your org from the Okta Integration Network catalog.
How the flow works
Setup is a one-time round trip: in Okta you register Serval as an XAA resource, register your agent, and grant the agent access; in Serval you trust your Okta org and register an API client for the agent. After that, every call is two steps — mint, then redeem.Set up Cross-App Access
1
Enable Serval as an XAA resource in Okta
In the Okta Admin Console, open the Serval app (add it from the OIN catalog first if needed). Go to Machine Assignments and find the Cross-App Access (XAA) card.Set it to Enabled, select the
serval:user scope, and save.The Cross-App Access (XAA) card defaults to Disabled. If you leave it disabled, Okta won’t mint ID-JAGs audienced to Serval and every exchange fails.
2
Register your AI agent in Okta
Go to Directory → AI Agents and register your agent (for example, the service behind Bifrost). Configure an Okta-hosted public/private key for it and Activate it.Copy the agent’s Client ID. Okta stamps this as the
client_id on every ID-JAG, and you register the same value in Serval in step 5.3
Give the agent access to Serval
On the agent, add a Resource connection:
- Resource type: Application
- App configured for AI Agent access
- Select the Serval app
- Scope:
serval:user
4
Trust your Okta org in Serval
In Serval, go to Settings → Security → Cross-App Access and Add Identity Provider (Trusted IdP):
- Issuer URL: your Okta org URL, e.g.
https://your-org.okta.com. Must match theissclaim on the ID-JAG exactly. - Audience (Client ID): enter
https://<your-serval-api-host>. Despite this field’s “Client ID” label and0oabc123...placeholder, the OIN app’s audience is the URL above — not an Okta client ID. It must equal the ID-JAGaudclaim. (For the custom resource-app model, this is instead that app’s client ID — see the API reference.) - Subject Claim:
email.
5
Register the agent as a Serval API client
Still under Cross-App Access, go to API Clients and Add Client:
- Client ID: the same value as the Okta agent’s Client ID from step 2. It must equal the
client_idclaim on the ID-JAG, or Serval rejects the exchange. - Display Name: a human-readable label.
6
Make sure the end user exists in both places
The user the agent acts as must exist on both sides with the same email:
- Okta: assign the user to the Serval app.
- Serval: an active user with that email, in the organization that owns the Trusted IdP.
7
Verify the exchange end to end
From your backend, mint an ID-JAG and redeem it (see Under the hood for the full calls). A successful redeem returns a Serval access token:Use that token as a
Bearer credential against https://<your-serval-api-host> (including the Serval MCP endpoint). If the redeem fails, the error / error_description in the response maps directly to a fix in Troubleshooting.Under the hood
Your integration code does two things on each call. Everything above was one-time setup. 1. Mint an ID-JAG from Okta. Your agent performs RFC 8693 token exchange against your Okta org’s token endpoint, authenticating as the agent client and requesting an assertion audienced to Serval (https://<your-serval-api-host>) with scope serval:user. The exact request — including how the acting user is supplied (e.g. a subject_token) and the client authentication method (private_key_jwt) — is handled by your Okta agent configuration or SDK. See Cross-App Access in the API reference for the full token-exchange contract and a worked example.
The API reference’s worked example shows the custom resource-app model, so adapt two things for the OIN AI-agent setup in this guide: set
audience to https://<your-serval-api-host> (not the example’s $OKTA_RESOURCE_CLIENT_ID), and authenticate as the agent with its Okta key via private_key_jwt rather than the example’s HTTP Basic source-client secret. Your Okta agent SDK handles the private_key_jwt assertion.POST to the token endpoint with HTTP Basic auth of your Serval API client (client_id:client_secret from step 5). This half is identical across all setups:
serval:user. Use it as a Bearer credential against https://<your-serval-api-host>.
Requirements checklist
- The audience is exactly
https://<your-serval-api-host>(your deployment’s host, not necessarily the cloud host). - The requested scope is exactly
serval:user. - The Serval API client’s Client ID equals the agent’s Okta
client_id. - The ID-JAG’s email maps to an active Serval user in the Trusted IdP’s org.
- ID-JAGs are short-lived and single-use: mint one per exchange, redeem promptly, never replay.
Troubleshooting
The redeem response is a standard OAuth error:{ "error": "...", "error_description": "..." }. Match on both.
Fix
invalid_client before debugging the assertion: a wrong client ID or secret returns invalid_client, and you won’t see a detailed invalid_grant description until your API client authenticates successfully.error_description.
