> ## Documentation Index
> Fetch the complete documentation index at: https://docs.serval.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Okta

> Okta is a cloud identity and access management platform; Serval connects to it to read and manage users, groups, and applications, run access reviews, and automate identity lifecycle tasks.

## About Okta

Okta is a cloud identity and access management (IAM) platform for managing users, groups, and application access. Serval connects to Okta using OAuth 2.0 client credentials against an Okta API Services app. Once connected, Serval can reach any Okta Management API capability - reading and managing users, groups, and applications, running access reviews, and automating identity lifecycle tasks. Serval also continuously brings your Okta directory into Serval in the background so users, groups, and applications stay up to date.

**Authentication:** OAuth 2.0 client credentials - either a client secret (client\_secret\_basic) or a private key JWT (private\_key\_jwt).

**Data sync:** On-demand access to the full Okta Management API from workflows, plus a built-in background directory sync of users, groups (with members), and applications (with their assigned groups). Optionally, [Okta event hooks](#real-time-sync-with-event-hooks) push user changes to Serval in real time.

## What the Okta integration enables

| Capability                         | Description                                                                                                                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Full Okta Management API access    | Any Okta Management API capability can be used from a workflow, with automatic retries on transient errors, pagination handling, and rate-limit awareness.                                                                                          |
| User lifecycle management          | Create users, look up a user by email, deactivate users, and suspend users (which clears their active sessions first, then suspends them). Requires the user read and/or manage scopes.                                                             |
| Group management and membership    | Create groups, look up a group by exact name, list all groups, and add or remove a user from a group. Requires the group read and/or manage scopes.                                                                                                 |
| Background directory sync          | Continuously brings Okta users, groups (with members), and applications (with their assigned groups) into Serval on a schedule - groups refresh fully every 48 hours with a delta sync every 4 hours; users and apps sync on the standard schedule. |
| Real-time sync via event hooks     | Optionally, an Okta event hook notifies Serval the moment a user changes, and Serval refreshes just that user within seconds - see [Real-time sync with event hooks](#real-time-sync-with-event-hooks).                                             |
| Access management and provisioning | Grants and revokes access by adding or removing users from Okta groups (using the "Member" entitlement). Requires the group manage scope.                                                                                                           |

Anything defined in the [Okta Management API](https://developer.okta.com/docs/reference/core-okta-api/) can be accessed through Serval.

## Get your credentials

Serval connects to an Okta API Services app using OAuth 2.0 client credentials. You can either install Serval from the Okta Integration Network (OIN), which generates credentials for you, or create your own custom Okta API Services app. In both cases you must grant the required scopes on the Okta app **and** assign the app an admin role so those scopes can actually act on resources. Full provider guidance lives in Okta's [client credentials grant guide](https://developer.okta.com/docs/guides/implement-grant-type/clientcreds/main/).

<Tabs>
  <Tab title="OIN install (recommended)">
    <Steps>
      <Step title="Open the Okta app catalog">
        In your Okta admin console, browse the Okta app catalog and search for **Serval** (or visit the [Serval listing on the Okta Integration Network](https://www.okta.com/integrations/serval/)).
      </Step>

      <Step title="Add and authorize the integration">
        Add the integration, review the requested API scopes, and authorize it. Okta generates a Client ID and a Client Secret for your org.
      </Step>

      <Step title="Copy the Client Secret immediately">
        <Warning>
          The Client Secret is shown only once. Copy it right away - you will not be able to view it again later.
        </Warning>
      </Step>

      <Step title="Assign an admin role">
        Under your admin-role assignments, give the app an admin role such as Super Administrator, a combination like Group / Organization / Application Administrator, or a least-privilege custom role with a resource set. Scopes plus the admin role together govern what Serval can do.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Custom app - client secret">
    <Steps>
      <Step title="Create an API Services app">
        In the Okta admin console, create a new app integration of type **API Services**, name it (for example, "Serval"), and save it.
      </Step>

      <Step title="Grant API scopes">
        Grant the required scopes to the app - at minimum the read scopes for users, groups, and apps; add the manage scopes to let Serval take action. Okta groups these under the User, Group, Application, and SystemLog tags.
      </Step>

      <Step title="Assign an admin role">
        Under your admin-role assignments, assign the app an admin role (Super Administrator, a set such as Group / Organization / Application Administrator, or a custom role plus resource set).

        <Note>
          A scope alone is not enough. Scopes gate which parts of the API are available; the admin role gates which resources Serval can actually touch.
        </Note>
      </Step>

      <Step title="Copy the credentials">
        Copy the Client ID (shown at the top of the app integration page) and the Client Secret to paste into Serval.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Custom app - private key">
    <Steps>
      <Step title="Create an API Services app">
        In the Okta admin console, create a new app integration of type **API Services**, name it (for example, "Serval"), and save it.
      </Step>

      <Step title="Disable DPoP">
        In the app's general settings, **uncheck** the "Require Demonstrating Proof of Possession (DPoP)" option.

        <Warning>
          Serval does not support DPoP / proof-of-possession. If it is left enabled, token use will be rejected.
        </Warning>
      </Step>

      <Step title="Switch to public/private key authentication and add a key">
        Set client authentication to the public-key / private-key method, add a key, generate it, and copy the key. Save it when prompted.

        <Warning>
          The key is not saved until you confirm it - verify it persisted before leaving the page. The key must be an RSA key.
        </Warning>
      </Step>

      <Step title="Grant API scopes and assign an admin role">
        Grant the required scopes and assign the app an admin role under your admin-role assignments, exactly as for the client-secret path.
      </Step>

      <Step title="Copy the credentials">
        Copy the Client ID (shown at the top of the app integration page) and the full generated key to paste into Serval.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Connect in Serval

Enter your credentials into the Serval connect form. The same fields are used whether you authenticate with a client secret or a private key - Serval automatically picks the right method based on what you paste into the Client Secret field.

<Steps>
  <Step title="API Domain (required)">
    Your Okta domain without the leading "https\://" - for example, "yourcompany.okta.com". Enter the bare host with no scheme and no "-admin" suffix.

    <Warning>
      Do not paste the admin console URL. If your console shows "yourcompany-admin.okta.com", strip the "-admin" and enter "yourcompany.okta.com". An invalid domain shows: "Must be a valid Okta domain (e.g., yourcompany.okta.com)".
    </Warning>
  </Step>

  <Step title="Client ID (required)">
    The OAuth 2.0 client ID from your Okta application - exactly 20 alphanumeric characters. An invalid value shows: "Must be a valid Okta client ID (20 alphanumeric characters)".
  </Step>

  <Step title="Client Secret (required)">
    This field is masked. Paste **either** your plain OAuth 2.0 client secret **or** the full private key you generated in Okta. Serval automatically uses the private-key method when you paste a key, and the client-secret method otherwise.

    <Note>
      When editing an existing connection, you can leave this field blank to keep the stored secret unchanged. The stored secret is always shown masked, never in clear text.
    </Note>
  </Step>

  <Step title="Scopes (required)">
    A comma-separated list of OAuth scopes. Serval pre-fills a default set covering app, user, and group read and manage plus log read. These scopes must also be granted on the Okta app itself. Not all of the pre-filled scopes are required - you can remove any scopes you don't need.

    <Warning>
      An empty list is rejected. Each scope must follow the expected format, otherwise you will see: "Each scope must follow the format okta.\<resource>.\<operation> or okta.\<resource>.\<subresource>.\<operation> (e.g., okta.users.read, okta.accessRequests.catalog.read). SSF scopes are ssf.\<operation> (e.g., ssf.read, ssf.manage)."
    </Warning>
  </Step>
</Steps>

## Verifying the connection

After you connect, Serval runs a series of health checks against your Okta org and reports the result of each one. Each health check is an API call that exercises a specific scope, so the checks always run regardless of which scopes you configured - a check whose scope you removed will simply fail with a missing-scope message. This is expected if you intentionally trimmed the scope list, and you can safely ignore failures for capabilities you don't plan to use.

* **List Okta Apps** confirms Serval can list applications. On success it reports "Successfully listed" followed by the number of apps found. On failure it reports "Unable to list apps." If the required scope is missing in Serval, it reports "Missing required scope in Serval: okta.apps.read".
* **List Okta Users** confirms Serval can list users. On success it reports "Successfully listed" followed by the number of users found. On failure it reports "Unable to list users." If the scope is missing, it reports "Missing required scope in Serval: okta.users.read".
* **List Okta Groups** confirms Serval can read groups. On success it reports "Successfully listed" followed by the number of groups found. On failure it reports "Unable to list groups." If the scope is missing, it reports "Missing required scope in Serval: okta.groups.read".
* **Manage Okta Groups** creates a temporary test group (named with a "serval-healthcheck-" prefix) and immediately deletes it to verify write access. On success it reports "Successfully created and deleted test group:" followed by the test group's name. On failure it reports "Unable to create and delete groups." If the scope is missing, it reports "Missing required scope in Serval: okta.groups.manage".

When a check fails with a permission error, Serval adds: "The correct scope is configured in Serval, but the OAuth application may need admin consent or additional admin roles in Okta." When Okta returns a server error, Serval adds: "Okta returned a server error. This is likely a temporary issue with Okta's API - please try again later."

<Tip>
  The most common surprise: the scope is configured correctly in Serval but a check still fails with a permission error. This almost always means the Okta app is missing the admin role (or admin consent). Assign the app an admin role under your admin-role assignments - a scope on its own is not enough.
</Tip>

## Real-time sync with event hooks

By default, Serval learns about changes in Okta on the background sync schedule, so a change can take hours to appear. With an Okta event hook configured, Okta notifies Serval the moment a user changes, and Serval re-fetches just that user - changes typically appear in Serval within seconds.

This is optional and additive: the background directory sync keeps running either way, acting as a safety net for anything the event hook misses.

No additional OAuth scopes are required - event hooks reuse the same `okta.users.read` scope your directory sync already uses.

<Note>
  If you don't see the **Webhooks** section described below on your Okta application's Integration tab, make sure user sync is enabled for this Okta app - the section appears once user ingestion is configured. If it's still missing, contact **[support@serval.com](mailto:support@serval.com)**.
</Note>

<Steps>
  <Step title="Copy the webhook details from Serval">
    In Serval, go to **Applications**, open your Okta application, and switch to the **Integration** tab. The **Webhooks** section shows two values: a **Webhook URL** (on Serval's public API host) and a **Secret**. Copy both.

    <Note>
      The Secret can be re-copied from this page at any time - unlike Okta, Serval does not hide it after setup.
    </Note>
  </Step>

  <Step title="Create the event hook in Okta">
    In the Okta admin console, go to **Workflow → Event Hooks** and choose **Create Event Hook**. Give it a name (for example, "Serval real-time sync") and fill in:

    * **URL**: the Webhook URL copied from Serval.
    * **Authentication field**: `X-Webhook-Secret`
    * **Authentication secret**: the Secret copied from Serval.
    * **Custom header fields**: leave empty.

    <Warning>
      Paste the Secret exactly as copied - no quotes and no trailing whitespace. Serval compares the value character-for-character and rejects anything that doesn't match.
    </Warning>
  </Step>

  <Step title="Subscribe to events">
    The **Subscribe to events** box starts empty - add the user events Serval acts on. Okta lists them by friendly name; these are the ones that trigger a refresh (API event type in parentheses):

    * User created (`user.lifecycle.create`)
    * User activated (`user.lifecycle.activate`)
    * User deactivated (`user.lifecycle.deactivate`)
    * User suspended (`user.lifecycle.suspend`)
    * User unsuspended (`user.lifecycle.unsuspend`)
    * User's Okta profile updated (`user.account.update_profile`)

    Any other event types are safely ignored, and any extra `user.*` event simply re-fetches that user, so subscribing to more does no harm.
  </Step>

  <Step title="Verify the hook">
    Save the hook and click **Verify**. Okta sends a one-time verification challenge to the Webhook URL, which Serval answers automatically.

    <Check>
      The hook shows as "Verified" in Okta. From this point on, changes flow to Serval as they happen.
    </Check>
  </Step>
</Steps>

## Gotchas and troubleshooting

<AccordionGroup>
  <Accordion title="A health check fails on permissions even though the scope is set in Serval">
    Scopes typed into the Serval form are only the scopes requested on the token. They must **also** be granted on the Okta API Services app, and the app must be assigned an admin role (or a custom role plus resource set). Scopes gate which part of the API is available; the admin role gates which resources can be acted on. Add the missing scope on the Okta app and assign it an admin role under your admin-role assignments.
  </Accordion>

  <Accordion title="EMEA (okta-emea.com) domains fail at request time">
    Serval only connects to "okta.com" and "oktapreview\.com" hosts. The connect form's validation will accept an "okta-emea.com" domain, but requests to EMEA-cell domains will fail once they actually run. Use an "okta.com" or "oktapreview\.com" domain.
  </Accordion>

  <Accordion title="The API Domain is rejected or requests go to the wrong host">
    The API Domain field expects a bare host such as "yourcompany.okta.com". Do not include "https\://", and do not copy the admin console URL - "yourcompany-admin.okta.com" is wrong; strip the "-admin" to get "yourcompany.okta.com". The domain is used as both the token host and the API base, so a wrong value breaks everything downstream.
  </Accordion>

  <Accordion title="Private key authentication isn't being used (or fails)">
    Serval decides between the plain-secret and private-key methods purely by whether the Client Secret value is a key (in JSON form). For key-based authentication, paste the **full** key into the Client Secret field; anything that is not a JSON key is treated as a plain client secret. The key must be an RSA key - a non-RSA key fails token generation with "key is not an RSA private key".
  </Accordion>

  <Accordion title="Tokens are rejected after enabling private key auth">
    Serval does not support DPoP / proof-of-possession. In the custom API Services app's general settings, uncheck the "Require Demonstrating Proof of Possession (DPoP)" option before connecting, otherwise token use will be rejected.
  </Accordion>

  <Accordion title="Updating the connection wiped or kept the secret unexpectedly">
    On update, Serval still requires Scopes, Client ID, and API Domain, but the Client Secret is optional. Leave it blank to keep the existing stored secret unchanged, or paste a new value to replace it. The stored secret is always returned masked.
  </Accordion>

  <Accordion title="Requests are being throttled / rate limited">
    Okta enforces roughly 250 requests per minute per application on the groups and apps endpoints. Serval paces background syncs to stay under this limit and automatically retries when throttled, but heavy custom workflows against these endpoints can still hit Okta's rate limits.
  </Accordion>

  <Accordion title="Event hook verification fails in Okta">
    Check the URL first: it must be the exact Webhook URL copied from the Webhooks section of your Okta application's Integration tab in Serval, with no edits. A 404 during verification almost always means the URL is wrong or truncated. If the URL is correct and verification still fails, re-copy the Secret from Serval and re-save the hook - remember it must be pasted exactly into the `X-Webhook-Secret` authentication field.
  </Accordion>

  <Accordion title="The event hook is verified but changes don't appear in Serval">
    Confirm the hook is subscribed to the event types listed in [Real-time sync with event hooks](#real-time-sync-with-event-hooks) - a verified hook with no matching subscriptions delivers nothing useful. Also check Okta's event hook preview/delivery log to confirm Okta is sending events and receiving successful responses. If deliveries succeed but nothing changes in Serval, the secret may be wrong: Serval acknowledges every delivery (so Okta shows success) but rejects events whose `X-Webhook-Secret` value doesn't match. Re-copy the Secret from Serval and update the hook. The scheduled background sync still keeps data correct in the meantime.
  </Accordion>
</AccordionGroup>

***

Need help? Contact **[support@serval.com](mailto:support@serval.com)** for assistance with your Okta integration.
