Control API - users and authenticator apps
Use the FoxIDs Control API to provision internal users and to manage the authenticator apps registered by each user. This guide focuses on authenticator app operations that can be used to synchronise registrations between FoxIDs deployments.
Before calling these operations, configure Control API authentication and access rights. Swagger remains the exact reference for all user properties, filters, validation rules, and response schemas:
Endpoint base
The examples use FoxIDs Cloud:
https://control.foxids.com/api/{tenant_name}/{track_name}
Replace {tenant_name} with the tenant name and {track_name} with the environment's technical name. Change the host for a self-hosted deployment. Send the Control API access token in the Authorization: Bearer {access_token} header.
User and authenticator app operations require a user access right for the target environment. Grant only the required read, create, update, or delete operation through the Control API access-right hierarchy.
User identifiers
Every internal user must have at least one of these sign-in identifiers: email, phone number, or username. Email addresses and usernames are normalised to lower case, and surrounding whitespace is removed from all identifiers.
The single-user operations identify a user by an email, phone, or username query parameter. The user response also contains a server-generated userId. This ID is unique and persistent, including when a sign-in identifier is changed, and should be used when associating authenticator apps or other resources with the user.
User operations
Individual user operations are grouped under tenant users in Swagger.
| Operation | Endpoint | Success | Purpose |
|---|---|---|---|
| List | GET /!users |
200 OK |
List and filter users with pagination. |
| Get | GET /!user?email={email} |
200 OK |
Get one user by email, phone number, or username. |
| Create | POST /!user |
201 Created |
Create one user with optional password credentials. |
| Update | PUT /!user |
200 OK |
Replace the editable user properties and optionally change sign-in identifiers. |
| Delete | DELETE /!user?email={email} |
204 No Content |
Delete one user by email, phone number, or username. |
| Bulk create or replace | PUT /!users |
204 No Content |
Import new users or replace matching users. |
| Bulk delete | DELETE /!users |
204 No Content |
Delete users identified in the request body. |
| Set password | PUT /!usersetpassword |
200 OK |
Set, import, or remove a password without the current password. |
| Change password | PUT /!userchangepassword |
200 OK |
Change a password by providing the current and new passwords. |
| Password history | GET, PUT, or DELETE /!userpasswordhistory |
200 OK / 204 No Content |
Read, replace, or delete a user's password history. |
Use phone={phone} or username={username} instead of email={email} in the single-user examples when appropriate. Append each endpoint to the endpoint base.
List and filter users
GET /!users accepts filterEmail, filterPhone, filterUsername, filterUserId, and filterClaimValue. Each filter performs a case-insensitive contains search. When several filters are supplied, a user is returned when any filter matches.
The response contains a data collection and an opaque paginationToken. To read the next page, repeat the same request and pass the returned value as paginationToken. Continue until the response no longer contains a token. Do not interpret or modify the token.
The user response reports whether a password is configured and when it was last changed, but it never returns the password or its current hash.
Create a user
The create request supports account state, verified identifiers, custom claims, password policy selection, password setup by email or SMS, and per-user MFA settings. For example, this request creates a user without a password and requires password setup through email:
POST https://control.foxids.com/api/{tenant_name}/{track_name}/!user
Authorization: Bearer {access_token}
Content-Type: application/json
{
"email": "alice@example.com",
"emailVerified": true,
"setPasswordEmail": true,
"claims": [
{
"claim": "role",
"values": ["employee"]
}
]
}
A create request can contain a plaintext password, supported password hash fields, or no password credentials. Never send both password formats. Plaintext passwords are checked against the selected password policy; imported hashes are not policy checked. If no credentials are supplied, use setPasswordEmail or setPasswordSms when the user should establish a password during sign-in.
Creating a duplicate user returns 409 Conflict, and the tenant's plan user limit applies. A concurrent user operation can temporarily prevent creation; retry a 423 Locked response after a short delay.
Update a user
PUT /!user is a full update, not a patch. Read the current user, preserve the properties that should remain unchanged, apply the intended changes, and send the complete editable representation. The claims collection and the account/MFA flags are replaced by the values in the request. Password credentials and authenticator app registrations are managed through their dedicated operations.
Identify the existing user with email, phone, or username. To change an identifier, keep its current value in that field and set updateEmail, updatePhone, or updateUsername to the new value. Set an update property to an empty string to remove that identifier; omit it to leave the identifier unchanged. Ensure the user retains at least one sign-in identifier.
Changing disableAccount from false to true revokes the user's refresh token grants and active sessions. Re-enabling the account permits new sign-ins but does not restore revoked sessions.
Manage passwords
Use PUT /!usersetpassword for an administrative set or migration. It supports three modes:
- Provide
passwordto apply the selected password policy and update password history. - Provide
passwordHashAlgorithm,passwordHash, andpasswordHashSaltto import a supported precomputed hash without policy validation. - Omit both password formats to remove the user's current password.
The optional passwordLastChanged value is Unix time in seconds. Use changePassword to require the user to choose a new password at the next applicable sign-in.
Use PUT /!userchangepassword when the current password is known. It verifies the current password and validates the new password against the user's password policy and history.
The password-history endpoint is intended for controlled migration and recovery scenarios. Its detail response contains password hash material, and PUT replaces the complete history. Treat it with the same safeguards as imported password hashes and do not log request or response bodies.
Bulk provisioning
PUT /!users accepts between 1 and 1,000 users per request. At most 100 entries can contain plaintext passwords because each password must be securely hashed. Requests with no plaintext passwords, including requests with supported precomputed hashes, can contain up to 1,000 users.
Bulk upload creates new users or replaces matching users; it does not merge individual properties and cannot rename email, phone, or username identifiers. Treat it as a replacement import. Use the single-user update operation for normal lifecycle changes where existing user state and the persistent user ID must be retained.
DELETE /!users accepts between 1 and 1,000 email addresses, phone numbers, or usernames in userIdentifiers. For import formats, performance guidance, and the seed tool, see Upload many users.
Delete users and revoke access
Deleting a user, individually or in bulk, revokes the user's refresh token grants and active sessions before deleting the account. This is a permanent account operation. Use the authenticator app delete operations when only one or all authenticator app registrations should be removed while keeping the user.
Create, update, password, and delete requests are included in the Control audit log. Read operations are not written as audit events.
Authenticator app registrations
Authenticator app operations are grouped under tenant user authenticator apps in Swagger. A list operation returns safe registration summaries, while the detail operation returns the sensitive data required for controlled synchronisation.
| Operation | Endpoint | Success | Purpose |
|---|---|---|---|
| List | GET /!userauthenticatorapps?userId={userId} |
200 OK |
Return registration IDs and creation times without secrets or recovery code hash data. |
| Get | GET /!userauthenticatorapp?userId={userId}&id={id} |
200 OK |
Return one registration, including its TOTP secret and recovery code hash data. |
| Create | POST /!userauthenticatorapp |
201 Created |
Create a registration with a client-supplied unique ID. |
| Update | PUT /!userauthenticatorapp |
200 OK |
Replace the secret and recovery code hash data for the selected user ID and registration ID. |
| Delete | DELETE /!userauthenticatorapp?userId={userId}&id={id} |
204 No Content |
Delete one registration. |
| Delete all | DELETE /!userauthenticatorapps?userId={userId} |
204 No Content |
Delete every authenticator app registration for the user. |
Append each endpoint to the endpoint base. For example:
GET https://control.foxids.com/api/{tenant_name}/{track_name}/!userauthenticatorapps?userId=e061ed17-7b44-48a8-b224-ecdb800ed5cc
Authorization: Bearer {access_token}
Registration resource
Create and update use the full registration resource:
{
"userId": "e061ed17-7b44-48a8-b224-ecdb800ed5cc",
"id": "7a772286-76a2-4f17-a0f8-4e927bb1772d",
"createTime": 1785769200,
"secret": "TOTP-SHARED-SECRET",
"recoveryCode": {
"hashAlgorithm": "P2HS512:10",
"hash": "RECOVERY-CODE-HASH",
"hashSalt": "RECOVERY-CODE-SALT"
}
}
The properties have the following behaviour:
userIdis the stable FoxIDs user ID.idis a persistent registration ID supplied by the client on create. It selects the registration on update and cannot be changed.createTimeis expressed as Unix seconds. It is optional on create and defaults to the current time. If omitted on update, the existing value is preserved.secretis the TOTP shared secret and is required for create and update.recoveryCodecontains recovery code hash data and is omitted when no recovery code is configured.
A user can have at most five authenticator app registrations. Creating a duplicate registration ID returns 409 Conflict; creating a registration after the limit is reached returns 400 Bad Request.
Synchronise registrations
A synchronisation service can combine the interactive Authenticator App notification API with these Control API operations:
- Receive the
registerednotification containinguser_idandregistration_id. - Get that registration from the source deployment using the detail operation.
- Create the registration in the target deployment, or update it when the same registration ID already exists.
Control API create, update, and delete operations do not call the Authenticator App notification API. Synchronising a registration therefore does not create a notification loop.
Security
The detail, create, and update operations expose or accept an authenticator app secret and recovery code hash data. Grant the required user access right only to trusted clients, use TLS, protect payloads at rest, and do not log request or response bodies.
Use the list operation when only registration IDs and creation times are needed. It does not return the secret or recovery code hash data.
Compatibility property
The activeTwoFactorApp property on the general user API is deprecated and planned for removal after 1 August 2027. In an update request, false still deletes all authenticator app registrations for compatibility; true or an omitted value leaves registrations unchanged. New integrations should use the authenticator app endpoints.
Error responses
Common user and authenticator app error responses include:
400 Bad Requestwhen identifiers, credentials, filters, or resource data are invalid, or the authenticator app registration limit is reached.401 Unauthorizedwhen the access token is missing or invalid.403 Forbiddenwhen the caller does not have the required user operation access right for the environment.404 Not Foundwhen the user or selected authenticator app registration does not exist.409 Conflictwhen a user already exists or an authenticator app create request uses an existing registration ID.423 Lockedwhen a user create or bulk import is temporarily locked. Retry the operation after a short delay.
Use the response body for validation details. Consult Swagger UI for the responses declared by each operation.