Admin API
Automate IdRa with its Okta-compatible management API (/api/v1) and event hooks.
IdRa's management API at /api/v1 is compatible with Okta's Core Management API for Users, Groups (and group rules), the System Log, Applications, Authorization Servers, Policies, Network Zones, Event Hooks and User Risk. Okta SDKs and scripts work against it.
Authentication
Create a token in Security → API → Tokens → Create token (Super Administrators only). It's shown once and starts with idra_. Send it as:
Authorization: SSWS idra_…A token acts with its creator's current role: a Read-only Administrator's token can read but gets 403 E0000006 on writes. Revoking a token takes effect immediately.
When you create a token you can also set:
- Expires after: 1 to 365 days. Empty means it doesn't expire.
- Allowed networks: the Network Zones it may be used from. Empty means any network.
Blocked zones refuse every API token, whatever its settings. A refused or expired token answers 401 E0000011, and a network refusal adds security.api_token.network_denied to the System Log. Zones match on the client IP, so TRUST_PROXY must be set for your proxy: without it IdRa sees no client IP, tokens restricted to a zone never work, and blocked IP zones don't match.
OAuth scoped tokens
Service apps can call /api/v1 with short-lived tokens limited to the scopes they need. This is the better choice for anything long-running.
- Create an OIDC app of type API Services.
- In Security → API → Tokens → Service app scopes, choose its scopes. Or call
POST /api/v1/apps/{id}/grantswith{"scopeId":"okta.users.read"}. - Request a token with the app's client ID and secret.
resourceis your org's URL:
curl -u "$CLIENT_ID:$CLIENT_SECRET" https://idra.example.com/api/auth/oauth2/token \
-d grant_type=client_credentials -d scope=okta.users.read \
-d resource=https://idra.example.com- Send it as
Authorization: Bearer <access_token>.
| Resource | Scopes |
|---|---|
Users (/users) | okta.users.read, okta.users.manage |
User roles (/users/{id}/roles) | okta.roles.read, okta.roles.manage |
User risk (/users/{id}/risk) | okta.userRisk.read, okta.userRisk.manage |
Groups (/groups) | okta.groups.read, okta.groups.manage |
System Log (/logs) | okta.logs.read |
Apps (/apps) | okta.apps.read, okta.apps.manage |
App scope grants (/apps/{id}/grants) | okta.appGrants.read, okta.appGrants.manage |
Authorization servers (/authorizationServers) | okta.authorizationServers.read, okta.authorizationServers.manage |
Policies (/policies) | okta.policies.read, okta.policies.manage |
Network Zones (/zones) | okta.networkZones.read, okta.networkZones.manage |
Event Hooks (/eventHooks) | okta.eventHooks.read, okta.eventHooks.manage |
GET needs the read or manage scope; other methods need manage.
- Who it acts as: a service app acts with the current role of the admin who last changed its scopes. A request needs both the scope and that role's permission, and tokens stop working if that admin is deactivated. Changes appear in the System Log under that admin, each with a
system.oauth_token.useevent naming the service app. - Refusals: a missing scope or role permission answers
403 E0000006. An expired or forged token, a token for another audience or org, and a token whose grant was removed answer401 E0000011. Removing a scope applies to tokens already issued. - Limits: tokens work on
/api/v1only, not on the console's API./users/meanswers 404, because a service app isn't a person. A service app can authenticate with a client secret orprivate_key_jwt, but tokens must be plain bearer tokens, not DPoP-bound. The Okta SDKs' and Terraform provider's OAuth mode doesn't work yet, because it calls Okta's token path. Use/api/auth/oauth2/token, or an SSWS token with those tools.
curl -H "Authorization: SSWS $TOKEN" \
"https://idra.example.com/api/v1/users?search=profile.email%20sw%20%22ann%22&limit=2"import okta from '@okta/okta-sdk-nodejs';
const client = new okta.Client({
orgUrl: 'https://idra.example.com',
token: process.env.IDRA_TOKEN,
authorizationMode: 'SSWS',
});
for await (const user of await client.userApi.listUsers()) console.log(user?.profile?.email);Conventions
- Paging:
limitplus an opaqueaftercursor; follow theLink: <…>; rel="next"header. Users, Network Zones, Policies and Event Hooks: 200 per page at most. Groups and members: 200 by default, 1000 at most. System Log: 100 by default, 1000 at most. - Rate limits: 600 requests a minute per token. Every response carries
X-Rate-Limit-Limit,-Remainingand-Reset; over the limit returns429 E0000047. - Errors: Okta's body,
{errorCode, errorSummary, errorLink, errorId, errorCauses}. Validation and unsupported filters →400 E0000001; unknown ids →404 E0000007; bad token →401 E0000011. - Filters:
eq ne sw co ew gt ge lt le prwithand/or/ parentheses, onid,status,created,lastUpdated,profile.login,profile.emailandprofile.<attribute>for users;id,type,created,lastUpdated,lastMembershipUpdated,profile.name,profile.descriptionfor groups;eventType,published,actor.id,target.id,outcome.result,client.ipAddress(and more) for the System Log.
Endpoints
| Resource | Endpoints |
|---|---|
| Users | GET|POST /users, me works as the id on every /users/{id} path below and means the token's admin, GET|POST|PUT|DELETE /users/{idOrLogin} (DELETE deactivates, a second DELETE deletes), POST /users/{id}/lifecycle/{activate|reactivate|deactivate|suspend|unsuspend|unlock|reset_password|reset_factors}, POST /users/{id}/credentials/change_password |
| User details | GET /users/{id}/{groups|appLinks|factors}, DELETE /users/{id}/factors/{factorId}, DELETE /users/{id}/sessions, GET|POST /users/{id}/roles, DELETE /users/{id}/roles/{roleId}, GET|PUT /users/{id}/risk |
| Groups | GET|POST /groups, GET|PUT|DELETE /groups/{id}, GET /groups/{id}/users, PUT|DELETE /groups/{id}/users/{userId}, GET /groups/{id}/apps |
| Group rules | GET|POST /groups/rules, GET|PUT|DELETE /groups/rules/{id}, POST /groups/rules/{id}/lifecycle/{activate|deactivate} |
| System Log | GET /logs (since, until, filter, q, sortOrder, limit) |
| Apps | GET|POST /apps, GET|PUT|DELETE /apps/{id}, POST /apps/{id}/lifecycle/{activate|deactivate}, /apps/{id}/users, /apps/{id}/groups, PUT /apps/{id}/policies/{policyId} |
| Authorization servers | /authorizationServers (+ /scopes, /claims, /policies, /policies/{id}/rules, lifecycle) |
| Policies | /policies?type=OKTA_SIGN_ON|ACCESS_POLICY|PASSWORD (+ rules, lifecycle) |
| Network zones | /zones (IP and DYNAMIC; usage: BLOCKLIST = blocked zone) |
| Event hooks | /eventHooks (+ lifecycle activate|deactivate|verify) |
Roles: SUPER_ADMIN, ORG_ADMIN, APP_ADMIN, HELP_DESK_ADMIN, READ_ONLY_ADMIN. App sign-on modes: BOOKMARK, OPENID_CONNECT, SAML_2_0.
Differences from Okta
- Users: no user types, linked objects, recovery questions,
expire_passwordornextLogin. The login is always the email and can't change. Profile keys must exist in the Profile Editor. IdRa always emails activation and reset links (sendEmail=falsereturns{}). - Group rules: a subset of Okta Expression Language (
user.<attr>=="v",String.startsWith|endsWith|stringContains,Arrays.contains); one group per rule; no exclusions. - Apps: no native OIDC apps,
private_key_jwt, implicit or password grants. Access Gateway apps are read-only. - Authorization servers: one access policy per server; static claims only.
- Policies:
OKTA_SIGN_ONandACCESS_POLICYcan't be created withPOST /policies; conditions IdRa can't enforce are rejected, never dropped. - Zones: always active; no regions or proxies.
Event hooks
Workflow → Event Hooks sends events to your endpoint as they happen. Create one with a name, an https endpoint URL and event type prefixes (user.lifecycle, group.user_membership, app.assignment, … or * for everything). The signing secret is shown once; Send test event checks the endpoint, and Deliveries shows the last 100 attempts.
Body:
{
"id": "…",
"type": "user.lifecycle.deactivate",
"outcome": "success",
"createdAt": "2026-10-30T12:00:00.000Z",
"actor": { "id": "…", "type": "user", "name": "admin@example.com" },
"target": { "id": "…", "type": "user", "name": "ann@example.com" },
"reason": null,
"client": { "ip": "203.0.113.4", "country": "FR", "userAgent": "…" },
"data": {}
}Each delivery has X-IdRa-Event-Id, X-IdRa-Event-Type and X-IdRa-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">. Verify it:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string) {
const { t, v1 } = Object.fromEntries(header.split(',').map((p) => p.split('=')));
if (!t || !v1 || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Non-2xx responses and network errors are retried (6 attempts in total, backing off up to an hour). Hooks created through /api/v1/eventHooks use Okta's body format instead and only receive events once verified.