Skip to main content
Cross-App Access (XAA) lets an AI agent or application registered in your Okta org call the Serval API on behalf of one of your users without sending that user through an interactive OAuth consent. The agent asks Okta for a short-lived assertion — an ID-JAG (OAuth Identity Assertion Authorization Grant, built on RFC 8693 token exchange) — and your backend redeems that assertion at Serval for a normal Serval access token. In this flow Serval is the resource app: Okta is the identity provider you already trust, and Serval accepts assertions it issues. This guide covers the Serval OIN app path, where the ID-JAG audience is your Serval API URL. If you instead register your own Okta resource app for Serval, its audience is that app’s client ID and the Okta-side values differ — follow the generic flow in the API reference for that model. Everything in Serval (Trusted IdP, API client, user resolution) and the redeem call is the same either way.
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 see https://<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.
Your region is fixed at organization creation. An EU organization must use public.eu1.serval.com everywhere — audience, token endpoint, and API calls. Requests to the US host will not reach EU data, and vice versa. See Data residency.

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
Do not use the Custom resource server / Resource Servers path. It does not register Serval as a valid ID-JAG audience, and the exchange fails with an audience error even though every other value looks right. This is the most common setup mistake.
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 the iss claim on the ID-JAG exactly.
  • Audience (Client ID): enter https://<your-serval-api-host>. Despite this field’s “Client ID” label and 0oabc123... placeholder, the OIN app’s audience is the URL above — not an Okta client ID. It must equal the ID-JAG aud claim. (For the custom resource-app model, this is instead that app’s client ID — see the API reference.)
  • Subject Claim: email.
Enable the identity provider.
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_id claim on the ID-JAG, or Serval rejects the exchange.
  • Display Name: a human-readable label.
Save, then copy the generated Client Secret and store it securely. It is shown once and is the credential your backend uses to authenticate to the token endpoint.
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.
Serval resolves the user by the ID-JAG’s email, scoped to that org. If your Serval org is provisioned by SCIM or a directory sync, confirm the user has synced before testing.
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.
2. Redeem the ID-JAG at Serval. 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 validates the ID-JAG against the Trusted IdP, resolves the user by email, and returns a Serval access token with scope serval:user. Use it as a Bearer credential against https://<your-serval-api-host>.
For the ID-JAG header/claim requirements and the exchange contract in full, see Cross-App Access in the API reference.

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.
If the exchange still fails after these checks, contact your Serval representative with the time of the attempt and the exact error_description.