Users and credentials

TOTP API

Authenticator-app MFA: enrolls TOTP devices for an identity, validates their codes at sign-in, and holds the tenant's TOTP settings (algorithm, digits, period, issuer).

https://{tenant-host}/totp-service/api/v1audience totp-service10 operations
Last checked against a live tenant: 28 October 2026checked live called on a tenantfrom source read in the service codenot confirmed not confirmed

When to use it

  • Enroll an authenticator app: start enrollment, show the QR code, check the first code.
  • Check a TOTP code at sign-in or step-up.
  • List, rename or delete a user's authenticator devices.
  • Import TOTP secrets when you migrate users from another system.

Connect

Base URL
https://{tenant-host}/totp-service/api/v1
Token
POST https://{tenant-host}/oauth/v1/token with grant_type=client_credentials. The client ID and secret go in the form body.checked live
Client to create
Access admin → Web clients → a client with the grant type Client credentials. Not an API client: those are only for Access's own configuration API.checked live
Default audiences
must include totp-servicechecked live
Default scopes
The totp:* scope each operation lists below. A tenant may not define these scopes in Access yet: then even the config read answers 403 "scope missing", and an Access admin must create the scopes first. Keep the client's scopes, joined with spaces, within 1024 characters.checked live
Tenant-Id header
Send Tenant-Id: <tenant-uuid> as for the other platform services. Tenant context comes from a shared library, so the exact header rules aren't confirmed.not confirmed
curl -s https://{tenant-host}/oauth/v1/token \
  -d grant_type=client_credentials \
  -d client_id=$CLIENT_ID \
  -d client_secret=$CLIENT_SECRET

curl -s https://{tenant-host}/totp-service/api/v1/totp/config \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Tenant-Id: $TENANT_ID"

Operations

Open an operation to see its parameters and code, and send it to your tenant. The token needs the listed scope. enroll is spelled with two l's.

Connect to your tenant

Then open any operation below and send it.

Sends grant_type=client_credentials to https://{tenant-host}/oauth/v1/token through this site's server. The secret and token stay in this tab only; host and client ID are remembered.

What trips people up

403 "scope missing" with the audience present

checked live

The token endpoint accepts totp-service and the service answers 403, not 401. The client lacks the totp:* scope, often because Access doesn't define TOTP scopes on that tenant yet. Create them in Access, then add them to the client.

Sign-in validation returns 200 for a wrong code

from source

POST /validate answers 200 with valid: false for a wrong code, a replayed code or a user without devices. Only valid: true (with deviceId) means the user passed. Don't branch on the status or on message.

Enrollment failures are 422, not 400

from source

A wrong first code is 422 TOTP-104. An expired or unknown transaction is 422 TOTP-103. Paused or disabled TOTP is 422 TOTP-108 / TOTP-107. Retry only on TOTP-104.

An enrollment lasts ten minutes

from source

Start enrollment returns enrollmentTransactionId. The user can retry a wrong first code with it for ten minutes. A wrong code doesn't extend that time. After it, start again: the old QR code is useless.

The secret is inside otpUri

from source

Start enrollment returns otpUri, deviceId, enrollmentTransactionId, algorithmName, codeDigits and timePeriodSeconds. There is no secret or QR image field: draw the QR code from otpUri. The URI contains the shared secret, so never log it and clear it after enrollment.

Send codes as strings

from source

code is a string of 6–8 digits. A number loses leading zeros.

The first code can't be reused at sign-in

from source

Enrollment marks its code's time step as used. A sign-in with the same code right after returns valid: false. Wait for the next code.

Clock skew counts whole periods

from source

The service accepts clockSkewSeconds / timePeriodSeconds steps on each side, rounded down. A skew below the period (e.g. 20 s with 30 s codes) means only the current code works. Enrollment always uses 30 s of skew.

Settings only apply to new enrollments

from source

Devices keep the algorithm, digits and period they were enrolled with. Changing the config doesn't rewrite them. The config PATCH is partial: omitted fields stay as they are.

Import needs every parameter and an empty identity

from source

Each imported device needs sharedSecret (Base32), algorithmName, codeDigits and timePeriodSeconds: no tenant defaults. If the identity already has any device, import answers 409 TOTP-106 instead of appending.

Errors

Error bodies carry id (trace id), code, message and details. A missing device on GET or DELETE is a 404 with an empty body. 401 and 403 come from the security layer and have no TOTP code.

400TOTP-100Invalid or missing parameters. Field errors are in details.
400TOTP-101Invalid or mismatched tenant.
404TOTP-102TOTP config not found for the tenant.
422TOTP-103Enrollment transaction missing or expired. Start again.
422TOTP-104Wrong first code during enrollment.
404TOTP-105Device not found (rename).
409TOTP-106Import refused: the identity already has a device.
422TOTP-107TOTP is disabled for the tenant.
422TOTP-108TOTP enrollments are paused. Sign-in still works.
404TOTP-109Authenticator state missing for the tenant.

When a call fails

ReplyMeansWhat to change
The service answers 401The token lacks this service's audience, or it expired.Add the audience to the client's Default audiences (Access admin → Web clients → the client), then get a new token.
The service answers 403 and the token has the audienceA scope the operation needs is missing.Add one of the operation's scopes to the client's Default scopes. Use the scope names your tenant lists under Access admin → Scopes.
Token endpoint: 401 invalid_clientUnknown client ID or wrong secret.Check the client ID and paste the secret again.
Token endpoint: 400 unauthorized_clientThe client may not use client credentials.Add the grant type Client credentials to the web client.
Token endpoint: 400 invalid_request about an audienceYou asked for an audience the client doesn't have.Add it to Default audiences, or stop asking for it.
Token endpoint: 500 right after scopes were addedThe client's Default scopes, joined with spaces, are over about 1024 characters.Move scopes to Additional scopes and request them per call, or split the work over two clients.
{"code":"not_found","message":"The requested location was not found"}That service isn't deployed on this tenant.Nothing to change in the client. Ask whoever runs the tenant.

Two kinds of client exist in Access. Service APIs like this one take a web client with the Client credentials grant. Access's own configuration API takes an API client: a web client sent there gets 401 invalid_credentials.