Skip to main content

Management API Reference

Base path: /api/v1. Every request needs Authorization: Bearer <access_token> from a client_credentials token issued to a Management API credential. Every action is scoped to that credential's own tenant — you can never read or change another tenant's data.

Request and response bodies are JSON with camelCase field names. PATCH endpoints only change the fields you send.

Errors​

Errors are returned as:

{ "error": "insufficient_scope", "error_description": "This credential does not have the 'ManageUsers' capability." }
StatuserrorMeaning
400invalid_requestMissing or invalid field, or the operation isn't allowed (the description says why)
401invalid_request / invalid_tokenNo bearer token, or the token is malformed, expired, revoked or issued to a suspended credential
403insufficient_scopeThe credential lacks the capability this endpoint requires
404not_foundNo such resource in this tenant

Capabilities at a glance​

Each endpoint group below names the capability it needs. Capabilities are ticked when you create the credential in Console → API Access.

CapabilityConsole labelCovers
ManageUsersManage usersUsers
ManageRolesManage rolesRoles, app roles, granting roles
RegisterAppsManage appsApps
ManageConnectorsManage app connectorsPer-app sign-in connectors
ManageTenantAdminsManage tenant adminsTenant administrators
ManageInvitationsManage invitationsInvitations
ManageServiceAccountsManage service accountsService accounts
ReadAuditLogRead audit logAudit events
ReadSessions / ManageSessionsRead / Manage sessionsUser sessions
ManageSessionPolicyManage session/token policySession, token and act-as settings
ReadRiskSignals / ManageRiskSignalsRead / Manage risk signalsRisk, alerts, trusted devices
ManageConnectedAppsManage connected appsWhich apps a user has authorized
ManageMfaResetsManage MFA resetsMFA reset requests
ManageSignInOptionsManage sign-in optionsPasskeys, device flow, cross-device magic link
ManageRelationshipTagsManage relationship tagsTags and their policies
ManageAccessRulesManage access rulesCountry restriction and per-user, per-tag, per-app and organization access rules
ManageRoleSyncManage role sync sourcesRole sync sources
ManageTenantConfigManage tenant configName, branding, "Powered by"
ManageCustomFieldsManage custom fieldsCustom field definitions
ManageDomainsManage domainsCustom domain
ManageDataExportManage data exportTenant data export
ManageMigrationRead migration statusDatabase migration status
ManageWebhooksManage webhooksWebhook endpoints
ManageNotificationSettingsManage notification settingsNotification channels

Grant a credential only what it needs — a token can never do more than the capabilities ticked on its credential.


Users — ManageUsers​

Create a user​

POST /api/v1/users
{ "email": "jane@example.com", "name": "Jane Doe" }
201
{ "userId": 42, "email": "jane@example.com", "status": "active" }

email is required. The user is created as a normal (end) user; the address is not marked verified until they prove it.

List users​

GET /api/v1/users?search=jane&status=active&limit=50&cursor=100
QueryMeaning
searchMatches name or any identifier (substring)
statusOnly users with this status, e.g. active, suspended
limitPage size, 1–200 (default 50)
cursorReturn users after this userId; use the previous response's nextCursor
200
{
"users": [{ "userId": 42, "email": "jane@example.com", "name": "Jane Doe", "status": "active", "createdAt": "2026-01-01T00:00:00Z" }],
"nextCursor": 42
}

nextCursor is null on the last page.

Get a user​

GET /api/v1/users/{id}

Returns one user (userId, email, name, status, createdAt).

Update a user​

PATCH /api/v1/users/{id}
{ "name": "Jane R. Doe" }

Returns the updated user, same shape as Get a user.

Deactivate / reactivate a user​

DELETE /api/v1/users/{id}
POST /api/v1/users/{id}/reactivate
200
{ "userId": 42, "status": "suspended" }

Deactivation is a soft suspension — the same as suspending a user in Console — never a hard delete. Reactivate returns "status": "active".

Lock, unlock and risk​

POST /api/v1/users/{id}/lock — ManageUsers
POST /api/v1/users/{id}/unlock — ManageUsers
GET /api/v1/users/{id}/risk — ReadRiskSignals
POST /api/v1/users/42/lock
{ "durationMinutes": 60, "reason": "investigating", "endSessions": true }
→ 200 { "userId": 42, "locked": true, "lockedUntil": "2026-09-28T11:30:00Z", "indefinite": false }

Send "indefinite": true instead of durationMinutes (1 to 2,628,000) to lock until an unlock. A lock blocks new sign-ins; endSessions also ends the person's current sessions. It is separate from suspend and is audited like the Console action. unlock clears a manual or automatic lock and the failed-attempt counter. risk returns { "userId", "score", "tier", "locked" } from the latest scored sign-in (score and tier are null before the first one).

Sessions — ReadSessions / ManageSessions​

GET /api/v1/users/{id}/sessions (ReadSessions)
DELETE /api/v1/users/{id}/sessions (ManageSessions) — sign the user out everywhere
DELETE /api/v1/users/{id}/sessions/{sessionRowId} (ManageSessions) — end one session
200
[{ "sessionId": 9, "ip": "203.0.113.4", "createdAt": "…", "expiresAt": "…", "revokedAt": null, "isTrusted": false }]

Revoking all returns { "userId": 42, "revokedCount": 3 }; revoking one returns { "userId": 42, "sessionId": 9, "revoked": true }.

Grant / revoke a role — ManageRoles​

POST /api/v1/users/{id}/roles { "roleId": 1 } → 201 { "userId": 42, "roleId": 1 }
DELETE /api/v1/users/{id}/roles/{roleId} → 200 { "userId": 42, "roleId": 1 }

Roles — ManageRoles​

Tenant-wide roles.

GET /api/v1/roles → [{ "roleId": 1, "name": "Support", "description": "…" }]
GET /api/v1/roles/{id}
POST /api/v1/roles { "name": "Support", "description": "Support team" } → 201
PATCH /api/v1/roles/{id} { "name": "…", "description": "…" }
DELETE /api/v1/roles/{id} → { "roleId": 1, "deleted": true }
GET /api/v1/roles/{id}/members → [{ "userId": 42, "email": "…", "name": "…" }]

name is required on create and update.

App-scoped roles​

Roles that belong to one app rather than the whole tenant.

GET /api/v1/apps/{appId}/roles
POST /api/v1/apps/{appId}/roles { "name": "Editor", "description": "…" } → 201
PATCH /api/v1/apps/{appId}/roles/{roleId} { "name": "…", "description": "…" }
DELETE /api/v1/apps/{appId}/roles/{roleId}

Apps — RegisterApps​

Register a new app​

POST /api/v1/apps
{ "name": "Internal Tool", "redirectUris": ["https://internal.yourapp.com/callback"] }
201
{ "appId": 7, "clientId": "b3f1...", "clientSecret": "sK9x..." }

name and at least one absolute redirectUris entry are required. The clientSecret is shown exactly once — store it immediately.

List, get, update, delete​

GET /api/v1/apps
GET /api/v1/apps/{id}
PATCH /api/v1/apps/{id}
DELETE /api/v1/apps/{id} → { "appId": 7, "deleted": true }
200
{ "appId": 7, "name": "Internal Tool", "clientId": "b3f1...", "redirectUri": "https://…/callback",
"clientType": "confidential", "requirePkce": true, "allowDeviceFlow": false, "createdAt": "…" }

PATCH accepts any of:

FieldMeaning
nameDisplay name
redirectUriThe primary redirect URI
additionalRedirectUrisExtra allowed redirect URIs (replaces the list)
requirePkceRequire PKCE for the authorization-code flow
allowEndUserSignInWhether ordinary users may sign in to this app
allowTenantAdminSignInWhether organization administrators may sign in to this app

At least one of allowEndUserSignIn / allowTenantAdminSignIn must stay true, otherwise the request is rejected with 400.

App access — RegisterApps​

GET /api/v1/apps/{id}/access
PUT /api/v1/apps/{id}/access { "restricted": true }
POST /api/v1/apps/{id}/access/users/{userId} add to the guest list
DELETE /api/v1/apps/{id}/access/users/{userId}
POST /api/v1/apps/{id}/access/tags/{tagId} let in everyone with a Relationship Tag
DELETE /api/v1/apps/{id}/access/tags/{tagId}
POST /api/v1/apps/{id}/access/exclusions/{userId} keep one person out of the group access
DELETE /api/v1/apps/{id}/access/exclusions/{userId}

GET returns { "appId", "restricted", "grantedUserIds", "grantedTagIds", "excludedUserIds" }. With restricted false everyone in the tenant can sign in and the lists are ignored; with true only the guest list and granted tags can. Removing access (revoke, remove a group, exclude, restrict) ends the affected people's app tokens immediately: refresh tokens are revoked and introspection reports their access tokens inactive.

Rotate a client secret​

POST /api/v1/apps/{id}/secret/rotate
200
{ "appId": 7, "clientSecret": "new-secret..." }

The old secret stops working immediately; the new one is shown once.

Per-app sign-in connectors — ManageConnectors​

GET /api/v1/apps/{id}/connectors → [{ "name": "EmailOtp", "enabled": true }]
PATCH /api/v1/apps/{id}/connectors { "name": "EmailOtp", "enabled": false }

A connector can only be switched on for an app once it has been approved for that app under Sign-in Methods in Console; otherwise the API returns 400.


Tenant administrators — ManageTenantAdmins​

GET /api/v1/admins → [{ "userId": 3, "email": "…", "name": "…", "role": "owner", "grantedAt": "…" }]
POST /api/v1/admins { "userId": 42, "role": "admin" } → 201 { "userId": 42, "role": "admin" }
DELETE /api/v1/admins/{userId} → { "userId": 42, "revoked": true }

role is owner or admin (default admin). The owner role can't be revoked, and the last remaining administrator can't be removed; a refused change returns 400 with the reason.

Invitations — ManageInvitations​

GET /api/v1/invitations?includeCompleted=false
POST /api/v1/invitations { "email": "new@example.com", "name": "New Person", "userKind": "end_user" }
DELETE /api/v1/invitations/{id}

userKind is end_user (default) or tenant_admin. Creating returns 201 with { "email", "userKind", "token" }; the token is the invitation credential. Only one pending invitation per email is allowed (400 otherwise). Listing returns invitationId, email, name, userKind, expiresAt, acceptedAt, revokedAt, createdAt. Revoking returns { "id": 5, "revoked": true }.

Service accounts — ManageServiceAccounts​

Machine identities (each Management API credential and machine-to-machine app has one).

GET /api/v1/service-accounts → [{ "userId": 8, "name": "HR sync", "status": "active", "createdAt": "…" }]
POST /api/v1/service-accounts { "name": "Nightly job" } → 201
POST /api/v1/service-accounts/{userId}/suspend → { "userId": 8, "status": "suspended" }
DELETE /api/v1/service-accounts/{userId} → { "userId": 8, "removed": true, "appsUnlinked": 1 }

Audit events — ReadAuditLog​

GET /api/v1/audit-events?since=2026-01-01T00:00:00Z&action=management_api.&userId=42&page=1&limit=50
QueryMeaning
sinceOnly events at or after this UTC time
actionAction prefix filter, e.g. management_api.
userIdOnly events about this user
page, limitPage number (from 1) and size (1–200, default 50)
200
{
"events": [{ "id": 1001, "action": "management_api.user_created", "target": "user:42", "userId": 42,
"userEmail": "jane@example.com", "appId": 7, "ip": "203.0.113.4", "at": "2026-01-01T00:00:00Z" }],
"totalCount": 120, "page": 1, "pageSize": 50, "totalPages": 3
}

Every write made through this API is itself recorded in the audit log (management_api.*), attributed to the calling credential.


Sessions, tokens and act-as — ManageSessionPolicy​

Each has a GET and a PATCH; PATCH returns the updated settings.

GET|PATCH /api/v1/session-policy
{ "defaultDurationHours": 8, "rememberMeAllowed": true, "rememberMeMaxDurationDays": 30 }

GET|PATCH /api/v1/token-policy
{ "accessTokenLifetimeSeconds": 900, "refreshTokenLifetimeDays": 30, "alwaysRequireConsent": false }

GET|PATCH /api/v1/act-as-settings
{ "enabled": true, "requireApproval": true }

token-policy also returns platformFloorAccessTokenSeconds and platformFloorRefreshTokenDays — the maximums your platform administrator allows. accessTokenLifetimeSeconds must be between 300 and that maximum, and refreshTokenLifetimeDays between 1 and its maximum; out-of-range values return 400.

Risk signals — ReadRiskSignals / ManageRiskSignals​

GET /api/v1/risk-events?days=14 (Read) summary of risky sign-in events
GET /api/v1/credential-health (Read)
GET /api/v1/session-intelligence (Read) concurrent sessions, multi-country sessions
GET /api/v1/security-alerts?limit=50 (Read) up to 200
POST /api/v1/security-alerts/{id}/acknowledge (Manage)
GET /api/v1/device-trust (Read) trusted devices
DELETE /api/v1/device-trust/{sessionId} (Manage) stop trusting a device

Alerts look like { "alertId", "alertType", "severity", "summary", "affectedUserCount", "detectedAt", "acknowledgedAt" }. Acknowledging an already-acknowledged or unknown alert returns 404.

Connected apps — ManageConnectedApps​

GET /api/v1/connected-apps → apps with distinctUserCount, activeNowCount, lastTokenIssuedAt
GET /api/v1/users/{userId}/connected-apps → the apps this user has authorized
DELETE /api/v1/users/{userId}/connected-apps/{appId} → { "userId": 42, "appId": 7, "revoked": true }

Revoking withdraws that user's access to the app.

MFA resets — ManageMfaResets​

GET /api/v1/mfa-resets
POST /api/v1/mfa-resets/{id}/approve { "note": "verified by phone" }
POST /api/v1/mfa-resets/{id}/deny { "note": "…" }

Requests include requestId, userId, userName, email, status, requestedAt, expiresAt and riskTier. Approve/deny return { "requestId", "userId", "status": "approved" | "denied" }; note is optional.


Sign-in options — ManageSignInOptions​

GET|PATCH /api/v1/signin-options
{
"webAuthnEnabled": true,
"webAuthnAllowAsAuthentication": true,
"webAuthnAllowAsMfa": true,
"deviceFlowEnabled": false,
"magicLinkCrossDeviceEnabled": true
}

Passkeys (WebAuthn) usage, the device-code flow, and cross-device magic links. These are still limited by what your platform administrator allows.

Access rules — ManageAccessRules​

GET /api/v1/geo-restriction
PUT /api/v1/geo-restriction { "mode": "allow", "countries": ["IN", "US"] } (mode: disabled | allow | deny)
GET /api/v1/geo-rules?userId=&appId=
POST /api/v1/geo-rules → 201 { "id" }
DELETE /api/v1/geo-rules/{ruleId} → 204

geo-restriction is the organization's country setting. geo-rules are the other rules (see Access Rules for behaviour and precedence). A rule has a scope (user, tag, app, org), a type and the fields for that type:

typeFields
country (default)effect (allow/deny), countries (two-letter codes)
regioneffect, regions ("IN:Karnataka")
ipeffect, cidrs (addresses or ranges)
hoursdays (0 = Sunday), startTime, endTime ("HH:mm"), timeZoneId
methodmethods: password, webauthn, EmailOtp, SmsOtp, MagicLink, Ldap, Saml, Social
anonymizernone (blocks VPN, proxy and Tor)
sessionsmaxSessions (1 to 50), for user, tag or org scope
sessionlengthmaxSessionMinutes (1 to 43200), for user, tag or org scope
devicesmaxDevices (1 to 20), for user, tag or org scope

Plus userId / tagId / appId for the scope, and optional reason and expiresAt. An invalid rule returns 400 invalid_request with a description.

Registered devices — ManageAccessRules​

GET /api/v1/users/{id}/devices
POST /api/v1/users/{id}/devices/{deviceId}/approve
POST /api/v1/users/{id}/devices/{deviceId}/deny
DELETE /api/v1/users/{id}/devices/{deviceId}

For the devices rule type. GET returns { "devices": [{ "deviceId", "label", "status", "firstSeen", "lastUsed", "ip", "place" }] } with status one of registered, pending or denied.

Relationship tags — ManageRelationshipTags​

GET /api/v1/relationship-tags
POST /api/v1/relationship-tags { "name": "Contractor" } → 201 { "tagId", "name" }
PATCH /api/v1/relationship-tags/{id}/policy { "policyKey": "…", "value": true }

Listing returns each tag with isDefaultForSignup, isDefaultForInvites and its policies (key/value pairs).

Role sync sources — ManageRoleSync​

GET /api/v1/role-sync-sources
POST /api/v1/role-sync-sources { "name": "Directory", "connectorName": "<installed role-sync connector>", "targetAppId": 7 } → 201
PATCH /api/v1/role-sync-sources/{id}/enabled true | false (raw JSON boolean body)
DELETE /api/v1/role-sync-sources/{id}

name and connectorName are required; targetAppId is optional. Listing returns sourceId, name, connectorName, targetAppId, enabled, lastSyncedAt.

Tenant configuration — ManageTenantConfig​

GET|PATCH /api/v1/tenant-config
{ "name": "Acme", "brandName": "Acme", "brandColor": "#0f766e", "brandLogoUrl": "https://…/logo.png",
"tagline": "…", "aboutDescription": "…", "termsUrl": "https://…", "privacyUrl": "https://…" }

GET|PATCH /api/v1/tenant-config/powered-by
{ "email": true, "error": true, "consent": true, "account": true, "apiHeader": true, "webhook": true }

GET tenant-config returns the branding fields; PATCH also accepts termsUrl and privacyUrl. Send only the fields to change. The powered-by PATCH sets all six surfaces — send the full object.

Custom fields — ManageCustomFields​

GET /api/v1/custom-fields?entity=user
POST /api/v1/custom-fields
DELETE /api/v1/custom-fields/{fieldName}
{ "fieldName": "employee_id", "displayLabel": "Employee ID", "fieldType": "text", "entity": "user",
"isMandatory": false, "isPii": false, "visibility": "user_private", "description": "…" }

POST creates the field, or updates it if fieldName already exists. Delete returns { "fieldName", "deleted": true }.

Custom domain — ManageDomains​

GET /api/v1/domains → { "identityMode", "slug", "customDomain", "verifyToken", "verifiedAt", "hasTlsCertificate" }
POST /api/v1/domains { "domain": "login.acme.com" } → 201
POST /api/v1/domains/verify → { "verified": true }

Set the domain, publish the DNS record using verifyToken, then call verify.

Data export — ManageDataExport​

GET /api/v1/data-export/preview → { "userCount", "appCount", "customFieldValueCount", "isLarge" }
GET /api/v1/data-export → the full tenant export as JSON

Each download is recorded in the audit log. Check preview first — large tenants produce large files.

Migration status — ManageMigration​

GET /api/v1/migration-status
{ "storageProvider": "sqlite", "migrationStatus": "idle", "migrationTargetProvider": null }

Read-only. Starting a migration is done from Console → Database & Migration.

Webhooks — ManageWebhooks​

GET /api/v1/webhooks
POST /api/v1/webhooks { "url": "https://…", "events": "*", "enabled": true, "description": "…", "appId": 7 }
PATCH /api/v1/webhooks/{id}
DELETE /api/v1/webhooks/{id}
GET /api/v1/webhooks/{id}/deliveries (latest 100)

Create returns 201 with { "webhookId", "url", "secret" } — the signing secret is shown once. events is a comma-separated list or * for all; appId optionally limits the endpoint to one app. Deliveries return deliveryId, eventType, status, httpStatus, attemptCount, errorMessage, createdAt, deliveredAt. For event formats and verifying signatures, see Webhooks.

Notification settings — ManageNotificationSettings​

GET|PATCH /api/v1/notification-settings
{ "enabledChannels": ["email", "sms"], "allowUserOverride": true }

GET returns channelStates (each channel's state for the tenant) and allowUserOverride.


Creating organizations​

Creating a new organization is not part of this tenant-scoped API. Platform operators who embed LoginLink in their own product use a separate credential — see the Platform API.