SeptemCore LogoSeptemCore
API ReferenceREST API

IAM REST API Reference

IAM (Identity & Access Management) REST API reference. 7 endpoint groups: Auth (login/logout/refresh/me), MFA (TOTP enable/verify/disable/recovery), Security Flows (verify-email/reset-password/invite), Providers (OAuth/wallet plugin), Users (CRUD/soft-delete/restore), RBAC (roles/permissions), Multi-Tenant (select/switch). All schemas documented.

The IAM service handles authentication, RBAC, user management, and pluggable auth providers. All endpoints are served at https://api.septemcore.com/v1.

See the REST API Overview for authentication headers, error format, pagination, and rate limiting.

For layout brevity, the /api/v1 base path prefix is omitted from the endpoint tables below.


Group 1: Authentication

EndpointAuthDescription
POST /auth/registerRegister new user (email + password). Creates tenant + Owner role.
POST /auth/loginEmail + password login. Returns token pair or requiresTenantSelection.
POST /auth/login/:providerIdLogin via OAuth/wallet provider. Returns redirect URL.
GET /auth/callback/:providerIdOAuth callback handler. Returns token pair or requiresTenantSelection.
POST /auth/logoutInvalidate refresh token. Subsequent refresh with this token → 401.
POST /auth/refreshRotate access token using refresh token. Returns new token pair.
GET /auth/meCurrent authenticated user with roles and permissions.
POST /auth/impersonateusers.impersonatePlatform Owner only. Get scoped JWT for a tenant. Requires reason.

POST /api/v1/auth/login

Request:

{
  "email":    "[email protected]",
  "password": "S3cur3P@ssw0rd!",
  "mfaCode":  "123456"
}

Response (single-tenant user):

{
  "accessToken":  "eyJhbGci...",
  "refreshToken": "rt_01j9p...",
  "expiresAt":    "2026-04-22T03:15:00Z"
}

Response (multi-tenant user — no lastTenantId):

{
  "requiresTenantSelection": true,
  "sessionToken": "st_01j9p...",
  "tenants": [
    { "id": "01j9pten0000000000000001", "name": "Acme Corp",  "logoUrl": "https://...", "role": "admin"  },
    { "id": "01j9pten0000000000000002", "name": "Beta Inc",   "logoUrl": "https://...", "role": "member" }
  ]
}

mfaCode is required only if the user has 2FA enabled. Omit when 2FA is off.

POST /api/v1/auth/impersonate

Request:

{
  "tenantId": "01j9pten0000000000000001",
  "reason":   "security_investigation",
  "note":     "Investigating suspicious login from IP 1.2.3.4 per ticket SEC-451"
}

Response: standard token pair with tenantId scoped to the target tenant. TTL: 1 hour. Audit record: auth.impersonation.started.


Group 2: MFA (Two-Factor Authentication)

EndpointAuthDescription
POST /auth/mfa/enableGenerate TOTP secret + QR code. User scans in Authenticator app.
POST /auth/mfa/verifyConfirm TOTP setup by entering a code. Activates 2FA.
POST /auth/mfa/disableDisable 2FA. Requires current password + active TOTP code.
POST /auth/recoveryEmergency login via recovery code. Allows resetting email+password+2FA.
GET /auth/recovery-codesReturns count of remaining recovery codes.
POST /auth/recovery-codes/regenerateGenerate 10 new recovery codes. Invalidates old codes.

Recovery Code Security

ParameterValue
Count per user10 codes, generated at registration and at 2FA enable
FormatRandom alphanumeric strings, each single-use
Storageargon2id hash in PostgreSQL — plaintext shown only once at generation
Rate limit5 attempts / 15 minutes (RECOVERY_MAX_ATTEMPTS). Exceeded → 1-hour lock + Owner email alert
Hard lockAfter 15 failed attempts (3 lockout cycles) → account status=locked + Platform Admin alert

Group 3: Security Flows

EndpointAuthDescription
POST /auth/verify-emailConfirm email address from verification token. TTL: 24h.
POST /auth/request-resetRequest password-reset email. One-time token sent via Notify.
POST /auth/reset-passwordReset password using one-time token from email. TTL: 1h.
POST /auth/inviteusers.createInvite user by email with pre-assigned role. TTL: 72h.
POST /auth/accept-inviteAccept invitation — set password, complete registration.

Request — POST /api/v1/auth/invite:

{
  "email": "[email protected]",
  "roles": ["support-manager"]
}

Group 4: Auth Providers

EndpointAuthDescription
GET /auth/providersList installed auth providers for login page rendering.
POST /users/:id/providers/:providerIdLink an additional auth provider to an existing account.
DELETE /users/:id/providers/:providerIdUnlink a provider. Rejected if it's the last linked provider.
GET /users/:id/providersList all provider links for a user.

Provider Object

{
  "id":          "01j9pprov000000000000001",
  "providerId":  "google",
  "providerType": "oauth",
  "externalId":  "accounts.google.com|117982...",
  "email":       "[email protected]",
  "displayName": "Google — [email protected]",
  "linkedAt":    "2026-04-01T09:00:00Z"
}

Uniqueness constraint: (providerId, externalId) is unique across all users. Attempting to link a Google account already linked to another user → 409 Conflict.


Group 5: Users

EndpointAuthDescription
POST /usersusers.createCreate user in the current tenant.
GET /usersusers.listList users (paginated). Default excludes deleted.
GET /users/:idusers.listUser details with roles and permission summary.
PATCH /users/:idusers.updatePartial update (name, email, status).
DELETE /users/:idusers.deleteSoft delete (blocks login, retains data).
PATCH /users/:id/restoreusers.updateRestore soft-deleted user (re-enables login).
POST /rbac/roles/:id/assignroles.assignAssign a role to user (body contains user_id).
POST /rbac/roles/:id/revokeroles.assignRevoke a role from user (body contains user_id).

User Object

{
  "id":              "01j9pusr0000000000000001",
  "email":           "[email protected]",
  "firstName":       "Alice",
  "lastName":        "Johnson",
  "avatarUrl":       "https://cdn.platform.io/assets/avatars/alice.webp",
  "tenantId":        "01j9pten0000000000000001",
  "roles":           ["admin", "billing-viewer"],
  "emailVerified":   true,
  "mfaEnabled":      true,
  "status":          "active",
  "createdAt":       "2026-04-01T10:00:00Z",
  "updatedAt":       "2026-04-20T14:30:00Z",
  "deletedAt":       null
}

RBAC Limits

ParameterLimitNotes
Max roles per user50Permissions union resolved at Gateway (Valkey cache)
Max permissions per role1,000Exceeding = 400 (rbac-limit-exceeded)
Max roles per tenant500Exceeding = 400 (rbac-limit-exceeded)

Group 6: RBAC — Roles & Permissions

Roles are created from atomic permissions. There are no built-in non-platform roles — tenant admins compose roles from available permissions.

EndpointAuthDescription
GET /rbac/rolesroles.listList all roles for this tenant (cursor-paginated).
POST /rbac/rolesroles.createCreate a new role with a set of permissions.
GET /rbac/roles/:idroles.listRole details with full permissions list.
PUT /rbac/roles/:idroles.updateUpdate role permissions.
DELETE /rbac/roles/:idroles.deleteDelete custom role (built-in roles cannot be deleted).
GET /rbac/permissionsroles.listList all registered permissions from all modules.
GET /rbac/users/:id/permissionsusers.listResolved permission list for user (union of roles).

POST /api/v1/rbac/roles

{
  "name":        "Support Manager",
  "description": "Can read contacts, close tickets, cannot delete.",
  "permissions": [
    "crm.contacts.read",
    "crm.tickets.read",
    "crm.tickets.close",
    "audit.read"
  ]
}

Wildcard Permissions

PermissionEffect
crm.*Full access to all CRM module operations
crm.contacts.readGranular: read-only on contacts
billing.*Full billing access

Resolution: Gateway checks exact match first, then wildcard {module}.*. Platform-reserved permissions (system.*, platform.*) cannot be registered by modules.

Role Object

{
  "id":          "01j9prole000000000000001",
  "name":        "Support Manager",
  "description": "Can read contacts, close tickets.",
  "permissions": ["crm.contacts.read", "crm.tickets.read"],
  "tenantId":    "01j9pten0000000000000001",
  "createdBy":   "01j9pusr0000000000000001",
  "createdAt":   "2026-04-10T08:00:00Z"
}

Group 7: Multi-Tenant

EndpointAuthDescription
GET /auth/tenantsList all tenants the user belongs to.
POST /auth/select-tenant❌ (session)Select tenant during login flow. Returns full JWT.
POST /auth/switch-tenantSwitch active tenant without re-login. Returns new token pair.

POST /api/v1/auth/select-tenant

Called after receiving requiresTenantSelection: true from login:

{
  "sessionToken": "st_01j9p...",
  "tenantId":     "01j9pten0000000000000001",
  "rememberChoice": true
}

rememberChoice: true saves lastTenantId — next login auto-selects this tenant.

POST /api/v1/auth/switch-tenant

{
  "tenantId": "01j9pten0000000000000002"
}

Response: new full token pair scoped to the target tenant. The refresh token is tenant-scoped — one stolen token does not give access to the user's other tenants.


Error Reference

Error typeStatusTrigger
problems/invalid-credentials401Wrong email or password
problems/mfa-required4012FA enabled but mfaCode not provided
problems/mfa-invalid401TOTP code incorrect or expired
problems/token-expired401Access or refresh token expired
problems/account-locked403Account locked after failed recovery attempts
problems/forbidden403Insufficient permission for operation
problems/rbac-limit-exceeded400Role/permission count exceeds plan limit
problems/provider-already-linked409OAuth provider already linked to another user
problems/last-provider400Cannot unlink the last auth provider
problems/transfer-in-progress409Tenant ownership transfer already pending

On this page