Users API
Manage users within an organization. Users are the end-user accounts that authenticate through Porta's OIDC endpoints.
Base path: /api/admin/organizations/:orgId/users
Create User
POST /api/admin/organizations/:orgId/users| Field | Type | Required | Description |
|---|---|---|---|
email | string | ✅ | Email address (must be unique within org) |
given_name | string | First name | |
family_name | string | Last name | |
nickname | string | Nickname | |
password | string | Password (NIST SP 800-63B compliant) | |
phone_number | string | Phone number | |
locale | string | User locale (e.g., en) | |
picture | string | Profile picture URL |
{
"email": "alice@example.com",
"given_name": "Alice",
"family_name": "Smith",
"password": "a-secure-password-here"
}Response: 201 Created
Invite User
POST /api/admin/organizations/:orgId/users/invitePermission: user:invite
Creates a pending invitation and sends an enhanced invitation email. No account is created at invite time; the recipient's account is created only when they accept the invitation and set a password. Supports optional personal message, role/claim pre-assignment, and inviter tracking. Pre-assigned roles and claims are stored with the invitation and automatically applied on acceptance.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | ✅ | Email address |
givenName | string | First name (OIDC standard claim) | |
familyName | string | Last name (OIDC standard claim) | |
personalMessage | string | Personal message from the admin (max 500 chars, included in email) | |
roles | array | Roles to pre-assign on acceptance | |
roles[].applicationId | uuid | ✅ | Application the role belongs to |
roles[].roleId | uuid | ✅ | Role ID to assign |
claims | array | Custom claim values to pre-assign on acceptance | |
claims[].applicationId | uuid | ✅ | Application the claim belongs to |
claims[].claimDefinitionId | uuid | ✅ | Claim definition ID |
claims[].value | any | ✅ | Claim value |
locale | string | Locale for the invitation email (default: org default) |
Response: 201 Created. Returns 409 Conflict when the email already has an account in the organization or a live invitation already exists for it.
{
"data": {
"invitationId": "uuid",
"email": "user@example.com",
"invitationSent": true,
"expiresAt": "2026-01-08T00:00:00.000Z"
}
}Pre-assignment behavior:
- Referenced applications, roles, and claim definitions are validated at invite time
- Pre-assignments are applied automatically when the invitation is accepted
- If a role or claim is deleted between invitation and acceptance, that assignment is skipped (best-effort)
- A new invitation replaces any live invitation for the same email address
Preview Invitation Email
POST /api/admin/organizations/:orgId/users/invite/previewPermission: user:invite
Renders the invitation email without sending it. Returns the HTML, plain text, and subject line for admin review before sending.
| Field | Type | Required | Description |
|---|---|---|---|
email | string | ✅ | Recipient email (for template personalization) |
givenName | string | First name (OIDC standard claim) | |
familyName | string | Last name (OIDC standard claim) | |
personalMessage | string | Personal message to include | |
locale | string | Locale for rendering |
Response: 200 OK
{
"data": {
"html": "<html>...</html>",
"text": "Plain text version...",
"subject": "John Doe has invited you to Acme Corp"
}
}List Users
GET /api/admin/organizations/:orgId/usersSupports page, pageSize, search, status, sort, order parameters. Search queries match against email, given name, family name, and nickname.
Response: 200 OK — Paginated user list.
Get User
GET /api/admin/organizations/:orgId/users/:userIdResponse: 200 OK — Full user profile.
Update User
PUT /api/admin/organizations/:orgId/users/:userIdUpdatable fields: given_name, family_name, nickname, phone_number, locale, picture.
INFO
Email changes are not supported through this endpoint to prevent authentication issues.
Response: 200 OK
Status Lifecycle
Users have three possible statuses (UserStatus): active, inactive, and locked. Administrators can activate and deactivate users. The server uses locked only for automatic failed-login lockout and cooldown recovery. Invitation is a pending token flow, not a status and not an account: no user row exists until the recipient accepts. On acceptance the account is created active and the recipient sets a password.
Status Transition Endpoints
POST /api/admin/organizations/:orgId/users/:userId/deactivate
POST /api/admin/organizations/:orgId/users/:userId/activateEach returns 204 No Content.
Set Password
POST /api/admin/organizations/:orgId/users/:userId/password| Field | Type | Required | Description |
|---|---|---|---|
password | string | ✅ | New password (NIST SP 800-63B compliant) |
Passwords are hashed with Argon2id before storage.
Response: 200 OK
User Roles
See Roles & Permissions API for user-role assignment endpoints at:
/api/admin/organizations/:orgId/users/:userId/rolesUser Claims
See Custom Claims API for user claim value endpoints.
User 2FA
Manage two-factor authentication for individual users. All endpoints require the admin:user:2fa permission.
Get 2FA Status
GET /api/admin/organizations/:orgId/users/:userId/two-factor/statusPermission: admin:user:read
Returns the user's current 2FA enrollment status, method, and recovery code count.
Response (200):
{
"enabled": true,
"method": "email",
"totpConfigured": false,
"recoveryCodesRemaining": 10
}| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether 2FA is currently enabled |
method | "email" | "totp" | null | Active 2FA method, null if disabled |
totpConfigured | boolean | Whether a TOTP authenticator is configured |
recoveryCodesRemaining | number | Number of unused recovery codes |
Disable 2FA
POST /api/admin/organizations/:orgId/users/:userId/two-factor/disablePermission: admin:user:2fa
Force-disables 2FA for a user, removing their TOTP configuration and recovery codes. Protected by super-admin guard — cannot disable the super-admin user's 2FA.
Response (200):
{ "message": "Two-factor authentication disabled" }Error responses:
| Status | Reason |
|---|---|
| 400 | 2FA is not currently enabled for this user |
| 403 | Target user is the super-admin (protected) |
| 404 | User not found in this organization |
Reset 2FA
POST /api/admin/organizations/:orgId/users/:userId/two-factor/resetPermission: admin:user:2fa
Resets 2FA by disabling it and clearing all enrollment data, forcing the user to re-enroll. Protected by super-admin guard.
Response (200):
{ "message": "Two-factor authentication reset" }Regenerate Recovery Codes
POST /api/admin/organizations/:orgId/users/:userId/two-factor/recovery-codes/regeneratePermission: admin:user:2fa
Generates a new set of 10 recovery codes, invalidating all previous codes. The new plaintext codes are returned once — they cannot be retrieved again. Protected by super-admin guard.
Response (200):
{
"recoveryCodes": ["A1B2C3D4E5", "F6G7H8I9J0", "..."]
}Organization 2FA Policy
Manage the organization-level 2FA enforcement policy.
Get Policy
GET /api/admin/organizations/:orgId/two-factor/policyPermission: admin:org:read
Response (200):
{
"twoFactorPolicy": "optional"
}Update Policy
PUT /api/admin/organizations/:orgId/two-factor/policyPermission: admin:org:update
| Field | Type | Required | Description |
|---|---|---|---|
twoFactorPolicy | string | Yes | One of: optional, required_email, required_totp, required_any |
Response (200):
{
"twoFactorPolicy": "required_email"
}Get 2FA Summary
GET /api/admin/organizations/:orgId/two-factor/summaryPermission: admin:org:read
Returns aggregate 2FA enrollment statistics for the organization.
Response (200):
{
"totalUsers": 50,
"enabledCount": 35,
"disabledCount": 15,
"totpCount": 20,
"emailCount": 15,
"complianceRate": 0.7
}| Field | Type | Description |
|---|---|---|
totalUsers | number | Total users in the organization |
enabledCount | number | Users with 2FA enabled |
disabledCount | number | Users without 2FA |
totpCount | number | Users using TOTP method |
emailCount | number | Users using email OTP method |
complianceRate | number | Ratio of enabled/total (0–1, 4 decimal places) |
Account Lockout
Porta automatically locks accounts after repeated failed login attempts (default: 5 attempts). Locked accounts auto-unlock after a cooldown period (default: 15 minutes). Administrators cannot manually lock or unlock accounts.
Lockout thresholds are configurable via the System Configuration API:
account_lockout_threshold— Number of failed attempts before auto-lock (default:5)account_lockout_cooldown_minutes— Minutes before auto-unlock (default:15)
See the Deployment Guide for full details.
GDPR Data Export
GET /api/admin/organizations/:orgId/users/:userId/exportExports all personal data for a user as a JSON document (GDPR Article 20 — data portability). The export includes:
- User profile (email, name, phone, locale, etc.)
- Organization membership
- Role assignments (with role names and application context)
- Custom claim values (with claim definitions)
- Audit log entries related to the user
- Two-factor authentication enrollment status
- Active OIDC sessions and grants
Response: 200 OK — JSON document containing all user data.
Delete User
DELETE /api/admin/organizations/:orgId/users/:userIdPermanently deletes the user and owned identity and security data in one database transaction. Affected server-backed sessions and tokens are revoked. Historical audit entries remain under the configured audit retention policy and may still identify the deleted user.
Permission: admin:user:delete
Response: 204 No Content
Irreversible
Deletion cannot be undone. A control-plane user cannot be deleted when that would leave no other active user with the exact built-in porta-super-admin role.
Standalone User Routes
In addition to the org-scoped routes above, a set of standalone user routes is available at /api/admin/users/:userId. These provide direct access to user detail and mutation operations by user ID, without requiring the organization ID in the URL path.
Base path: /api/admin/users/:userId
These routes support administrative clients that navigate by user ID without an organization slug. They delegate to the same service functions and require the same admin authentication and RBAC permissions as the organization-scoped routes.
Available Standalone Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
GET | /:userId | Get user by ID | user:read |
PUT | /:userId | Update user profile | user:update |
POST | /:userId/deactivate | Deactivate user | user:lifecycle |
POST | /:userId/activate | Activate user | user:lifecycle |
POST | /:userId/password | Set password | user:update |
DELETE | /:userId/password | Clear password | user:update |
POST | /:userId/verify-email | Mark email verified | user:update |
GET | /:userId/history | Change history | user:read |
TIP
The activate endpoint performs the inactive → active status transition.