At a glance
Start
4006 in the default set, or on http://localhost:4000 when started alone. Without a seed config there is one user ([email protected]), the built-in Everyone group, the default authorization server, a confidential OAuth client (okta-test-client, secret okta-test-secret, redirect URI http://localhost:3000/callback), a public PKCE client (okta-test-app), and one OIDC app (Sample OIDC App).
The @okta/okta-sdk-nodejs client works unchanged with orgUrl set to the emulator:
Auth
Every/api/v1/* route needs Authorization: SSWS <token> (or Bearer); a missing or unknown token is 401 E0000011 (“Invalid token provided”). From the CLI any non-empty token works.
OAuth for Okta: the Management API and /oauth2/v1/clients also take Authorization: Bearer <access token> from the org authorization server (/oauth2/v1/token) when the token has the scope for the request: okta.<resource>.read or okta.<resource>.manage for GET, okta.<resource>.manage otherwise, for users, groups, apps, authorizationServers, and clients, and okta.clients.register for registering a client. A missing scope is 403 E0000006 with WWW-Authenticate: Bearer error="insufficient_scope". Tokens from custom authorization servers are for your own APIs and get 401 E0000011.
Management API conventions
Lists use Okta’s cursor pagination:limit and after. Each response carries a Link header with rel="self" and, while more remain, rel="next", whose URL holds the cursor; the SDK’s collections follow it. Default and maximum page sizes follow Okta (users 200, groups 10000, group members 1000, apps 20 of up to 200, app users 50 of up to 500, authorization servers 200).
search and filter take Okta’s expression language: eq, ne, gt, ge, lt, le, sw, co, ew, and pr, joined with and, or, not, and parentheses, with quoted values (profile.email eq "[email protected]" and status eq "ACTIVE"). String comparisons ignore case. A malformed expression or an attribute Okta does not allow there is 400 E0000031. q matches the start of a name.
Errors use Okta’s shape (errorCode, errorSummary, errorLink, errorId, errorCauses). A missing resource is 404 E0000007 with Okta’s summary (Not found: Resource not found: <id> (User)), and a validation failure is 400 E0000001 (Api validation failed: login) with the field in errorCauses.
OAuth and OIDC
Default org server and custom authorization server paths (/oauth2/:authServerId/...):
GET /.well-known/openid-configuration— Get OIDC discovery document: default serverGET /oauth2/:authServerId/.well-known/openid-configuration— Get server discovery document: per authorization serverGET /oauth2/v1/keys— Get JSON Web Key Set (JWKS)GET /oauth2/v1/authorize— AuthorizePOST /oauth2/v1/token— Exchange tokenGET /oauth2/v1/userinfo— Get user infoPOST /oauth2/v1/revoke— Revoke tokenPOST /oauth2/v1/introspect— Introspect tokenGET /oauth2/v1/logout— End session
response_mode may be query (default), fragment (the code and state come back after #), or form_post. A refresh_token is issued only when the granted scope includes offline_access and the client has the refresh_token grant type, as on Okta. Each refresh rotates it. A scope parameter may narrow the new access token; asking for a scope outside the original grant is 400 invalid_scope, and the rotated refresh token keeps the original grant. A client without the refresh_token grant gets unauthorized_client.
Signing key
ID tokens are signed with a key stored in emulator state (kid: baseline-okta-1), so they stay valid across snapshot restores and persisted restarts. Set okta.signing_key.private_jwk to an RSA private JWK to pin the key. See OIDC signing keys.
Client registration
Okta’s Client Application API, theregistration_endpoint every discovery document lists (/oauth2/v1/clients, at the org level for every server). It needs a management token (Authorization: SSWS ...) or an org server access token with okta.clients.register (to register) or okta.clients.manage (see Auth).
POST /oauth2/v1/clients— Register client: RFC 7591.client_name(required),redirect_uris,application_type(web,native,browser,service),grant_types(authorization_code,refresh_token,client_credentials),response_types(code),token_endpoint_auth_method(client_secret_basic,client_secret_post,none). Returns201withclient_idand, for confidential clients,client_secret(client_secret_expires_at: 0)GET /oauth2/v1/clients— List clients:qmatches the start of the name,limitGET /oauth2/v1/clients/:clientId— Get client (without the secret)PUT /oauth2/v1/clients/:clientId— Replace client metadataDELETE /oauth2/v1/clients/:clientId— Delete clientPOST /oauth2/v1/clients/:clientId/lifecycle/newSecret— Rotate client secret
web clients use client_secret_basic and authorization_code, browser and native clients are public (none), and service clients use client_credentials. Bad metadata is 400 invalid_client_metadata; grant types the emulator does not run (implicit, password, device code) are refused the same way. A registered client can sign in through any authorization server.
Users
GET /api/v1/users— List users:searchtakes any attribute (id,status,created,activated,statusChanged,lastLogin,lastUpdated,passwordChanged,type.id,profile.*) withsortByandsortOrder;filtertakesid,status,lastUpdated,profile.login,profile.email,profile.firstName, andprofile.lastName;qmatches the start of the first name, last name, or email. Withoutsearchorfilter, deprovisioned users are left out, as on OktaPOST /api/v1/users— Create user:profile.login,profile.email,profile.firstName, andprofile.lastNameare required; login and email must look like email addresses and logins are unique (emails need not be). Other profile attributes (mobilePhone, custom attributes) are kept. Optionalcredentials.password.valueandgroupIds.?activate=falsecreates aSTAGEDuser,?nextLogin=changePasswordaPASSWORD_EXPIREDone. New users join the built-in Everyone groupGET /api/v1/users/me— Get current user: the token’s userGET /api/v1/users/:userId— Get user: by ID, login, or emailPOST /api/v1/users/:userId— Update user: partial update, only the profile attributes sent change, andnullclears one.credentials.passwordsets a passwordPUT /api/v1/users/:userId— Replace user: profile attributes left out are cleared, and the required ones must be sentDELETE /api/v1/users/:userId— Delete user: the first call deactivates, the second deletesGET /api/v1/users/:userId/groups— List user groupsPOST /api/v1/users/:userId/lifecycle/activate— Activate user: fromSTAGED,PROVISIONED, orDEPROVISIONEDtoACTIVE; an active user is403 E0000016. Answers{}, or{ activationUrl, activationToken }withsendEmail=false(the URL is not served)POST /api/v1/users/:userId/lifecycle/reactivate— Reactivate user: only forPROVISIONEDusers; answers like activatePOST /api/v1/users/:userId/lifecycle/deactivate— Deactivate user: toDEPROVISIONED, removing the user’s app assignmentsPOST /api/v1/users/:userId/lifecycle/suspend— Suspend user:ACTIVEtoSUSPENDED(400 E0000001otherwise)POST /api/v1/users/:userId/lifecycle/unsuspend— Unsuspend user:SUSPENDEDtoACTIVE(400 E0000001otherwise)POST /api/v1/users/:userId/lifecycle/unlock— Unlock user:LOCKED_OUTtoACTIVE(403 E0000032otherwise)POST /api/v1/users/:userId/lifecycle/expire_password— Expire password: toPASSWORD_EXPIRED; returns the userPOST /api/v1/users/:userId/credentials/change_password— Change password:oldPasswordandnewPassword; a wrong old password is403 E0000014
403 E0000038. Only ACTIVE and PASSWORD_EXPIRED users appear on the sign-in page, and a suspended or deactivated user’s codes and refresh tokens stop working.
Groups
GET /api/v1/groups— List groups:q,searchonid,type,created,lastUpdated,lastMembershipUpdated,profile.name,profile.description;filteronid,type,lastUpdated,lastMembershipUpdatedPOST /api/v1/groups— Create group (profile.namerequired and unique)GET /api/v1/groups/:groupId— Get groupPUT /api/v1/groups/:groupId— Replace group profileDELETE /api/v1/groups/:groupId— Delete group: with its memberships and app assignmentsGET /api/v1/groups/:groupId/users— List group membersPUT /api/v1/groups/:groupId/users/:userId— Add group member (updateslastMembershipUpdated)DELETE /api/v1/groups/:groupId/users/:userId— Remove group memberGET /api/v1/groups/:groupId/apps— List group apps: apps assigned to the group
Apps
GET /api/v1/apps— List apps:qon name or label;filteronstatus,name,user.id,group.idPOST /api/v1/apps— Create app (labelandsignOnModerequired;?activate=falsecreates itINACTIVE)GET /api/v1/apps/:appId— Get appPUT /api/v1/apps/:appId— Update appDELETE /api/v1/apps/:appId— Delete app: deletes anINACTIVEappPOST /api/v1/apps/:appId/lifecycle/activate— Activate app (answers{})POST /api/v1/apps/:appId/lifecycle/deactivate— Deactivate app (answers{})GET /api/v1/apps/:appId/users— List app users: direct (scope: "USER") and through groups (scope: "GROUP")POST /api/v1/apps/:appId/users— Assign user to app (id, optional appprofile)GET /api/v1/apps/:appId/users/:userId— Get app userPOST /api/v1/apps/:appId/users/:userId— Update app user profileDELETE /api/v1/apps/:appId/users/:userId— Unassign user from appGET /api/v1/apps/:appId/groups— List app group assignments: bypriorityPUT /api/v1/apps/:appId/groups/:groupId— Assign group to app (priority,profile)GET /api/v1/apps/:appId/groups/:groupId— Get app group assignmentDELETE /api/v1/apps/:appId/groups/:groupId— Remove app group assignment
OPENID_CONNECT app is an OAuth client, as on Okta. settings.oauthClient (redirect_uris, grant_types, response_types, application_type) is required and validated like client registration, and credentials.oauthClient may set token_endpoint_auth_method and a custom client_id. The response’s credentials.oauthClient carries the client_id (the app ID unless one was given) and client_secret. The client signs in through any authorization server while the app is active; deactivating the app makes it invalid_client, and deleting the app removes the client. A client registered at /oauth2/v1/clients is listed as an app too, and deleting either removes both.
Authorization servers
GET /api/v1/authorizationServers— List authorization servers (q,limit,after)POST /api/v1/authorizationServers— Create authorization server (name, optionalidandaudiences;201)GET /api/v1/authorizationServers/:authServerId— Get authorization serverPUT /api/v1/authorizationServers/:authServerId— Update authorization serverDELETE /api/v1/authorizationServers/:authServerId— Delete authorization serverPOST /api/v1/authorizationServers/:authServerId/lifecycle/activate— Activate authorization server (204)POST /api/v1/authorizationServers/:authServerId/lifecycle/deactivate— Deactivate authorization server (204)
Access policies
Each custom authorization server has access policies that decide which requests get tokens and how long the tokens last (OAUTH_AUTHORIZATION_POLICY policies with RESOURCE_ACCESS rules, as in the @okta/okta-sdk-nodejs authorizationServerApi):
GET|POST /api/v1/authorizationServers/:authServerId/policies,GET|PUT|DELETE .../policies/:policyId,POST .../policies/:policyId/lifecycle/activate|deactivateGET|POST .../policies/:policyId/rules,GET|PUT|DELETE .../rules/:ruleId,POST .../rules/:ruleId/lifecycle/activate|deactivate
conditions.clients.include, client IDs or ALL_CLIENTS). A rule matches grant types (conditions.grantTypes.include), people (conditions.people.users.include user IDs, conditions.people.groups.include group IDs or EVERYONE), and scopes (conditions.scopes.include, or *), and sets actions.token.accessTokenLifetimeMinutes (5 to 1440, default 60), refreshTokenLifetimeMinutes (0 is unlimited), and refreshTokenWindowMinutes (default 10080, seven days). Priorities stay 1 to n; creating or moving one to a taken priority shifts the rest down.
For each authorize, token, and refresh request, the first active policy by priority that names the client is used; its first active rule that matches the grant type, the user (client credentials match only EVERYONE), and every requested scope sets the lifetimes. No match is access_denied with “Policy evaluation failed for this request, please check the policy configurations.” (a redirect from /authorize, 400 from /token). Refresh tokens expire after the rule’s lifetime, or after the window passes without use.
The default server and seeded servers start with Okta’s Default Policy (all clients) and Default Policy Rule (everyone, any scope, one hour access tokens). A server created with POST /api/v1/authorizationServers starts with no policies, so it issues no tokens until you add one, as on Okta. Seed policies on a server to set its own (rules may name groups and users by name or login). The org server (/oauth2/v1) has no access policies.
Seed config
login, group name, app name, client_id, or server id), including the default [email protected], Everyone, okta-test-client, and okta-test-app, updates that record with the fields it sets.
Users take okta_id, status, display_name, locale, and time_zone; groups take okta_id and type; apps take okta_id, status, sign_on_mode, settings, and credentials. OAuth clients take response_types, grant_types, and token_endpoint_auth_method. Authorization servers take description, status, and policies (see Access policies). group_memberships (group_okta_id, user_okta_id) and app_assignments (app_okta_id, user_okta_id) link seeded records, and signing_key pins the ID token key (see Signing key).
Conformance
See CONFORMANCE.md for every provider.