Set up authentication with Okta
The Conversations API is in Controlled Availability. Endpoints, behaviors, and configuration surfaces may change during CA. Contact your Moveworks account team to participate.
The examples in this guide use https://api.moveworks.ai, which is only correct for orgs on the US Production data center. Replace it with the base URL for your data center from the Base URLs table (for example, https://api.am-eu-central.moveworks.ai for EU). Requests to the wrong host return 404 or invalid_audience.
This guide walks through configuring Okta to authenticate against the Moveworks Conversations API. By the end you will have Okta issuing access tokens for your users, and your application successfully calling the Conversations API on their behalf.
How authentication works
The Conversations API uses a token exchange model. Your application obtains an access token from Okta, exchanges it for a Moveworks bearer token, and uses the Moveworks bearer on Conversations API requests.
Get an Okta access token
Your application obtains an access token from Okta the standard way for its client type (Authorization Code + PKCE for a browser SPA, Authorization Code for a server-side web app).
Exchange the Okta token for a Moveworks bearer
Your application POSTs the Okta token to https://api.moveworks.ai/oauth/v1/token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Moveworks validates the signature, claims, and identity, then returns a short-lived opaque bearer token (mwt_...).
This guide covers user-facing clients where a real person signs in through Okta. For a backend service that acts on behalf of users without an interactive sign-in, use service-to-service authentication instead.
Prerequisites
Confirm the following before you start:
- Your Moveworks tenant is provisioned for the Conversations API and your account team has provided the Bot Name (used in the
Assistant-Nameheader on every API call). - You have admin access to the Moveworks Admin Portal (
https://{org}.moveworks.com). - You have admin access to your Okta org.
- The identifier your Okta
subclaim will carry (commonly the user’s email or login) resolves against your Moveworks user roster.
Step 1: Create the Okta authorization server
Custom vs Org authorization server. This guide uses a Custom Authorization Server. Okta also has a built-in “Org” (default) authorization server, but its audience is nominally fixed and behaves inconsistently in practice, which makes it harder to configure and debug. Use a Custom server unless you have a specific reason not to.
In the Okta Admin Console, go to Security → API → Authorization Servers → Add Authorization Server.
- Name: any descriptive label (for example
Moveworks-CAPI). - Audience: enter any placeholder for now if you plan to use a Moveworks-generated audience; you’ll set the real value in Step 3.1. If you intend to keep an audience value you already use, enter it here and register the same value in Moveworks in Step 2.
- Description: optional.
Click Save. Once created, capture two values from the auth server. You’ll paste both into Moveworks in Step 2.
1.1 Copy the Metadata URI
On the auth server’s Settings tab, find the Metadata URI field and copy its value. It looks like https://{yourOktaDomain}/.well-known/oauth-authorization-server/oauth2/{authServerId}. This is your Public Key value for the Moveworks credential.
1.2 Get the Issuer URL from the metadata JSON
Okta’s Settings tab shows “Issuer: Dynamic (based on request domain)” rather than a static URL. To get the real value, open the Metadata URI you copied in a browser tab. The response is JSON; find the "issuer" field near the top. It looks like https://{yourOktaDomain}/oauth2/{authServerId} (no .well-known/* in it). That’s your Issuer for the Moveworks credential.
Step 2: Register the credential in Moveworks
In the Moveworks Admin Portal, go to https://{org}.moveworks.com/http-connectors/connector_studio/home → Credentials → Create Credential. Set the fields as follows.
Two ways to set the Audience. Leave the Audience field blank and Moveworks generates a unique audience value on save, which you will paste into Okta in Step 3. Or, if your Okta authorization server already has an audience value you cannot change, enter that exact value here instead. Whichever path you choose, the Audience on the Moveworks credential and the Audience on the Okta authorization server must match byte-for-byte.
Click Save and then Publish. Moveworks displays three values back to you:
- Subject: Moveworks-internal ID for the credential.
- Audience: the credential’s audience. If you left the field blank, this is the unique value Moveworks generated (format:
api.moveworks.ai/{uuid}); copy it to paste into Okta in Step 3. If you entered your own value, confirm it displays exactly as you entered it. - Public Key 1 - ID: Moveworks-side identifier for the key it fetched from your Metadata URI. No action needed.
Step 3: Finish configuring the Okta authorization server
Return to the Okta Admin Console, Security → API → Authorization Servers, and open the authorization server you created in Step 1.
3.1 Align the audience
On the Settings tab, set Audience to match the Audience on your Moveworks credential: the generated api.moveworks.ai/{uuid} value from Step 2 if you left the field blank, or the value you entered yourself. If you registered your authorization server’s existing audience in Step 2, no change is needed here; just confirm the two match. Save.
The two values must match byte-for-byte. Any mismatch produces 401 Token validation failed at token exchange.
3.2 Confirm the sub claim value
On the Claims tab, find the sub claim on Access Token. The default expression on a Custom Authorization Server is:
With a signed-in user, that expression sets sub to the user’s userName (typically their email or login), which resolves against the Moveworks user roster.
If your Moveworks user roster uses a field other than the Okta userName (for example, if user.email differs from user.login in your org), override the sub expression accordingly. Otherwise, leave the default.
The access policy rule that links this authorization server to your OAuth application comes in Step 4.4, after you create the app.
Step 4: Create the Okta OAuth application
In the Okta Admin Console, go to Applications → Applications → Create App Integration. The dialog asks for two things:
- Sign-in method — choose OIDC - OpenID Connect. (The other option, API Services, issues tokens that represent the app itself rather than a signed-in user, which does not fit the Conversations API’s per-user model and is not covered by this guide. For backend integrations, use service-to-service authentication.)
- Application type — pick one:
- Single-Page Application for a browser-based client (React, Vue, Angular, etc.). Uses Authorization Code + PKCE.
- Web Application for a server-side client with a client secret. Uses Authorization Code.
- Native Application for a desktop or mobile app.
Click Next.
4.1 Configure the application
In the app’s General settings, add all redirect URIs your application will use to receive the auth code (for example https://your-app.example.com/callback).
4.2 Assign users or groups
On the app’s Assignments tab, assign the users or groups that should be able to authenticate through this application. Users must be assigned here or Okta will not issue tokens for them (even if the access policy in Step 4.4 allows it).
4.3 Add trusted origins (browser clients only)
Under Security → API → Trusted Origins, add your application’s domain with CORS and Redirect both enabled. Skip this step for server-side applications.
4.4 Attach the app to the authorization server via access policy
Return to Security → API → Authorization Servers → your Moveworks-CAPI server → Access Policies tab. Add or edit a policy, then add a rule with:
- Grant type: Authorization Code.
- User: any user assigned to the application (or a specific group).
- Scopes:
openid(or Any scopes). - Assigned to: the OAuth application you created in 4.1.
Without this rule, Okta rejects token requests for your app before Moveworks ever sees them.
Note your app’s Client ID. You need it in the next step. Web Applications also have a Client Secret; SPAs do not.
Step 5: Get an Okta access token
In production, your app’s Okta SDK handles the browser redirect, code exchange, and token capture automatically. For manual testing, use an OIDC playground such as oauth.tools, or run a local PKCE flow with a script and register http://localhost:PORT/callback as a redirect URI on your Okta application.
However you run the flow, the response includes an access_token field. Copy that value; it is your Okta access token. Decode it (for example at jwt.io) and confirm:
issmatches the Issuer you registered in Step 2 exactly.audmatches the Audience on your Moveworks credential (the generatedapi.moveworks.ai/{uuid}value, or the custom value you registered).submatches the identifier expected by your Moveworks user roster.
If any of these do not match, the exchange in Step 6 will fail. Re-check the corresponding fields in Steps 2 and 3.2 before continuing.
Step 6: Exchange the Okta token for a Moveworks bearer
Your application must complete this step before making any Conversations API call.
A successful response:
The mwt_... value is your Moveworks access token. It represents the user resolved from the Okta sub claim, expires in about an hour, and is the bearer you use on every Conversations API call.
Cache and refresh: cache this token for the duration in expires_in (typically ~1 hour). When it expires, get a fresh Okta token and repeat this exchange. Do not exchange on every API call; that pattern will hit rate limits.
Step 7: Call the Conversations API
Use the Moveworks bearer from Step 6 on every request. Include the Assistant-Name header (your Bot Name from your Moveworks account team).
A 201 response with a conversation_id confirms the whole chain works end to end.
Troubleshooting
401 UNAUTHORIZED with Token validation failed when calling /assistant/v1/*.
Check that your Authorization: Bearer value is the Moveworks bearer from Step 6 (starts with mwt_), not the raw Okta access token (starts with eyJ). If you’re already sending mwt_... and still hitting this error, the bearer has likely expired; repeat Step 6 with a fresh Okta token.
401 UNAUTHORIZED with Token validation failed at the exchange endpoint (/oauth/v1/token).
The Okta token was received but Moveworks rejected it. Decode the Okta token and confirm:
issmatches the Issuer field on your Moveworks credential (byte-for-byte, no trailing slash, correct case).audmatches the Audience field on your Moveworks credential exactly (whether Moveworks-generated or the custom value you registered in Step 2).submatches the identifier your Moveworks user roster resolves. Ifsubis an Okta internal user ID starting with00u, you’re looking at the ID token, not the access token. Decode the access token instead. Ifsubon the access token is still00u..., revisit Step 3.2 to override the default expression.- The credential in Moveworks Setup is Published (not draft).
- The token has not expired (
expis in the future).
Okta rejects the token request before Moveworks sees it (error at /authorize or /token).
Okta’s access policy or user assignment blocks the token issuance. Revisit Step 4.2 (assign the user to the app) and Step 4.4 (access policy rule allowing your app + grant type + scopes).
aud claim comes out as api://default in the token.
Your token is being issued by Okta’s pre-built default authorization server rather than the one you created in Step 1. Either point your app’s token requests at the right authorization server, or, since the Moveworks credential accepts any audience value, register api://default as the credential’s Audience. A dedicated authorization server with a distinct audience is easier to debug.
invalid_audience on a call to /oauth/v1/token.
Your Okta token’s aud does not match what’s registered on your Moveworks credential. Compare the decoded aud against the Audience field on your Moveworks credential exactly.
User is authenticated but Conversations API responses look wrong or empty.
The sub claim resolved to a Moveworks user, but potentially the wrong one. Check that the identifier in sub matches the user’s channel entry with integration_id: conversations_rest_api in Moveworks. If your Okta emits a different email or login format than what’s on the Moveworks user record, adjust either the Okta sub claim mapping (Step 3.2) or the user’s channel entry.
Summary of what you configured
For interaction patterns (polling and streaming) and the full API contract, see the Conversations API reference.