Blog · July 5, 2026

How to Create a Secure Okta API Service App (OAuth 2.0)

If you need a service, script, or SaaS integration to call the Okta management APIs, the right building block is an Okta API service app using the OAuth 2.0 client credentials flow. This guide walks through creating an Okta OAuth service app in the Admin Console, why private-key authentication beats a client secret, how to grant only the read scopes it needs, and how to enable DPoP so the token can't be replayed if it leaks.

What an API service app is — and when to use it

An Okta API service app is an OAuth 2.0 client that authenticates as itself, with no human in the loop, using the client credentials flow. It requests an access token from your org's authorization server and uses that token to call the Okta management APIs on behalf of the service — not on behalf of a user. This is the modern replacement for the old pattern of pasting a long-lived static SSWS API token into a config file.

Reach for a service app whenever a backend job, automation, or third-party tool needs programmatic access to Okta: syncing users, reading group memberships, exporting logs, or reading configuration to assess it. Prefer it over a static token because client-credentials tokens are short-lived, scoped, and bound to a key you control — not a single secret string that works forever until someone finds it in a repo.

Static API token vs. OAuth service app

A static SSWS token inherits the full permissions of whatever admin created it, never expires on its own, and grants the same broad access to anyone who copies it. There is no way to say "this token may only read users" — it can do everything its creator can do. That is a large, standing blast radius sitting in plaintext somewhere.

An OAuth service app flips every one of those properties: access is limited to the specific scopes you grant, tokens are short-lived and re-minted on demand, and authentication uses a private key rather than a shared secret. If you are choosing between the two for anything new, the OAuth service app is the defensible answer nearly every time.

Creating the Okta OAuth service app in the Admin Console

In the Okta Admin Console, go to Applications, then Applications, and choose Create App Integration. Select API Services as the sign-in method and give the app a clear, purpose-specific name so future admins know exactly what it does and why it exists. Okta creates the integration and issues it a client ID.

By default a new API service app is configured for the client credentials grant, which is exactly what you want for machine-to-machine access. The next two decisions — how it proves its identity, and what it is allowed to read — are where most of the security lives, so treat them deliberately rather than accepting defaults.

Choose Public key / Private key instead of a client secret

Okta lets an API service app authenticate with either a client secret or a public/private key pair, but for calling Okta's own APIs with Okta scopes, the private key JWT method (often written as private_key_jwt) is the supported and recommended path. Under the Client Credentials settings, pick Public key / Private key rather than Client secret.

The reason is simple: a client secret is a shared password that lives in your configuration, so anyone who obtains it can impersonate the app. With private_key_jwt, the app signs a short-lived assertion with a private key that never leaves your environment — Okta only ever holds the public key. You can store the key in Okta or host your public keys at a JWKS URL, which also makes rotation far cleaner than swapping a secret everywhere it's referenced.

Grant only the read scopes it needs

Scopes are where least privilege becomes concrete. On the app's Okta API Scopes tab, you grant individual scopes one at a time, and the app can request only what has been granted. For an integration that reads Okta configuration, grant scopes ending in .read — for example okta.users.read or okta.groups.read — and nothing more.

Resist the urge to grant a broad management scope "just in case." Every scope you add widens what a stolen token could do, and every scope you withhold is one less thing to worry about in an audit. If the integration genuinely only reads, it should hold only .read scopes, and any manage-level grant should be a conscious, documented exception.

Who grants the scopes — and what the app can't do

Granting OAuth scopes to a service app is a privileged action: only a Super Admin can approve scope grants, and this is a one-time setup step per scope. That approval is the human checkpoint — a Super Admin decides, once, exactly what this non-human client may touch.

Importantly, granting a .read scope is not the same as handing the app an admin role. The app's access is bounded by the scopes it was granted, so a read-only service app stays read-only even though a Super Admin set it up. Keep it that way: don't assign the service app an administrator role unless a specific API genuinely requires one, because that would quietly expand its reach well beyond the scopes you carefully chose.

Enable DPoP and test the connection

DPoP — Demonstrating Proof-of-Possession — binds the access token to the app's key pair, so the token only works when presented by the client that holds the matching private key. If a DPoP-bound token is intercepted, an attacker can't replay it, because they can't produce the proof. In the app's client credentials settings, require DPoP so Okta rejects any token request that doesn't include a valid DPoP proof.

To test, have the app sign its client assertion, request a token from your org's /token endpoint, and confirm you receive a DPoP-bound access token, then make one read call — listing a few users, say — and verify a 200 response. A clean round trip means authentication, scopes, and DPoP are all wired correctly and the integration is ready to use.

One habit worth adopting: periodically review every service app in your org, not just the one you're building. Scopes accumulate, ownership gets fuzzy, and an integration created to read one thing often ends up holding grants nobody remembers approving.

Atomation connects exactly this way — a read-only service app with private-key auth and DPoP, holding only .read scopes. It also flags over-privileged service apps in your org and lists the scopes each one was actually granted, so you can spot the ones quietly holding more than they need. See it on your own org, the demo is open, no signup: demo.atomation.io.

Get started

See which of your Okta service apps are over-privileged

Request a scoped Okta assessment. We'll align the baseline around your org count, reporting needs, evidence requirements, and delivery model.