IdRa

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.

  1. Create an OIDC app of type API Services.
  2. In Security → API → Tokens → Service app scopes, choose its scopes. Or call POST /api/v1/apps/{id}/grants with {"scopeId":"okta.users.read"}.
  3. Request a token with the app's client ID and secret. resource is 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
  1. Send it as Authorization: Bearer <access_token>.
ResourceScopes
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.use event 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 answer 401 E0000011. Removing a scope applies to tokens already issued.
  • Limits: tokens work on /api/v1 only, not on the console's API. /users/me answers 404, because a service app isn't a person. A service app can authenticate with a client secret or private_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: limit plus an opaque after cursor; follow the Link: <…>; 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, -Remaining and -Reset; over the limit returns 429 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 pr with and / or / parentheses, on id, status, created, lastUpdated, profile.login, profile.email and profile.<attribute> for users; id, type, created, lastUpdated, lastMembershipUpdated, profile.name, profile.description for groups; eventType, published, actor.id, target.id, outcome.result, client.ipAddress (and more) for the System Log.

Endpoints

ResourceEndpoints
UsersGET|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 detailsGET /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
GroupsGET|POST /groups, GET|PUT|DELETE /groups/{id}, GET /groups/{id}/users, PUT|DELETE /groups/{id}/users/{userId}, GET /groups/{id}/apps
Group rulesGET|POST /groups/rules, GET|PUT|DELETE /groups/rules/{id}, POST /groups/rules/{id}/lifecycle/{activate|deactivate}
System LogGET /logs (since, until, filter, q, sortOrder, limit)
AppsGET|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_password or nextLogin. 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=false returns {}).
  • 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_ON and ACCESS_POLICY can't be created with POST /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.

On this page