Skip to content

Authenticator (TOTP) ​

/api/devices

The Authenticator (TOTP) API lets you generate one-time passwords and manage virtual 2FA devices for testing authentication systems.

You can either save an authenticator device and request codes by device ID, or generate a current OTP directly from a Base32 shared secret without saving anything.

WARNING

Shared secrets are accepted as input only. Mailisk never returns a saved shared secret in API responses.

Endpoints ​

MethodEndpointDescription
GET/api/devicesList saved authenticator devices
POST/api/devicesCreate a saved device from shared_secret
POST/api/devices/customCreate a saved device with custom TOTP settings
POST/api/devices/base32-secret-keyCreate a saved device from base32_secret_key
POST/api/devices/otpauth-urlCreate a saved device from an otpauth:// URL
POST/api/devices/otpGenerate an OTP from a shared secret
GET/api/devices/{deviceId}/otpGenerate an OTP for a saved authenticator device
DELETE/api/devices/{deviceId}Delete a saved authenticator device

Generate OTP Without Saving ​

POST /api/devices/otp

Use this endpoint when a test already has a Base32 shared secret and only needs the current OTP.

This endpoint uses the default TOTP settings: 6 digits, a 30 second period, and SHA1. It does not accept custom digits, period, or algorithm values.

Pass min_seconds_until_expire when a test needs a code that will remain valid for at least a minimum number of seconds. If the current code is too close to expiry, Mailisk waits until the next TOTP period starts and returns the next code.

sh
curl --request POST \
     --url https://api.mailisk.com/api/devices/otp \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-Api-Key: {Api Key}' \
     --data '{
       "shared_secret": "JBSWY3DPEHPK3PXP",
       "min_seconds_until_expire": 10
     }'

Body ​

NameRequiredTypeDescription
shared_secretRequiredStringBase32 shared secret.
min_seconds_until_expireOptionalNumberMinimum seconds the returned code must remain valid. Defaults to 0. Must be less than the TOTP period, which is 30.

Response ​

json
{
  "code": "123456",
  "expires": "2026-05-18T12:00:30.000Z"
}

Create Saved Device ​

POST /api/devices

Use this endpoint for the simplest saved-device flow. It stores a Base32 shared secret using the default TOTP settings: 6 digits, a 30 second period, and SHA1.

sh
curl --request POST \
     --url https://api.mailisk.com/api/devices \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-Api-Key: {Api Key}' \
     --data '{
       "name": "GitHub staging",
       "shared_secret": "JBSWY3DPEHPK3PXP"
     }'

Body ​

NameRequiredTypeDescription
shared_secretRequiredStringBase32 shared secret.
nameOptionalStringHuman-readable device name. If omitted, Mailisk derives one.
expires_atOptionalStringFuture ISO timestamp after which the saved device expires.

Response ​

json
{
  "id": "9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273",
  "organisation_id": "7f0a9c32-66b2-4e25-a4cf-1f77db8f7f3b",
  "name": "GitHub staging",
  "username": null,
  "issuer": null,
  "digits": 6,
  "period": 30,
  "algorithm": "SHA1",
  "source": "shared_secret",
  "expires_at": null,
  "created_at": "2026-05-18T12:00:00.000Z",
  "updated_at": "2026-05-18T12:00:00.000Z"
}

Create Custom Saved Device ​

POST /api/devices/custom

Use this endpoint when you want to save an authenticator device and reuse it across test steps.

sh
curl --request POST \
     --url https://api.mailisk.com/api/devices/custom \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-Api-Key: {Api Key}' \
     --data '{
       "name": "GitHub staging",
       "secret": "JBSWY3DPEHPK3PXP",
       "issuer": "GitHub",
       "username": "qa@example.com",
       "digits": 6,
       "period": 30,
       "algorithm": "SHA1"
     }'

Saved-device creation can return 403 when TOTP devices are not available for the organisation or when the active saved-device limit has been reached. Expired devices do not count against that limit.

The organisation is inferred from the organisation API key.

Body ​

NameRequiredTypeDescription
nameOptionalStringHuman-readable device name. If omitted, Mailisk derives one.
secretRequiredStringBase32 shared secret.
usernameOptionalStringAccount label associated with the device.
issuerOptionalStringIssuer or application label associated with the device.
digitsOptionalNumberOTP length. Supported values are 6 and 8. Defaults to 6.
periodOptionalNumberTOTP period in seconds. Must be between 10 and 300. Defaults to 30.
algorithmOptionalStringOne of SHA1, SHA256, or SHA512. Defaults to SHA1.
expires_atOptionalStringFuture ISO timestamp after which the saved device expires.

Response ​

json
{
  "id": "9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273",
  "organisation_id": "7f0a9c32-66b2-4e25-a4cf-1f77db8f7f3b",
  "name": "GitHub staging",
  "username": "qa@example.com",
  "issuer": "GitHub",
  "digits": 6,
  "period": 30,
  "algorithm": "SHA1",
  "source": "custom",
  "expires_at": null,
  "created_at": "2026-05-18T12:00:00.000Z",
  "updated_at": "2026-05-18T12:00:00.000Z"
}

Create From Base32 Secret Key ​

POST /api/devices/base32-secret-key

This endpoint is equivalent to the custom create endpoint, but uses the base32_secret_key field name for compatibility with clients that expose a Base32 secret-key flow.

sh
curl --request POST \
     --url https://api.mailisk.com/api/devices/base32-secret-key \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-Api-Key: {Api Key}' \
     --data '{
       "name": "GitHub staging",
       "base32_secret_key": "JBSWY3DPEHPK3PXP",
       "issuer": "GitHub",
       "username": "qa@example.com",
       "digits": 6,
       "period": 30,
       "algorithm": "SHA1"
     }'

Body ​

NameRequiredTypeDescription
base32_secret_keyRequiredStringBase32 shared secret key.
nameOptionalStringHuman-readable device name. If omitted, Mailisk derives one.
usernameOptionalStringAccount label associated with the device.
issuerOptionalStringIssuer or application label associated with the device.
digitsOptionalNumberOTP length. Supported values are 6 and 8. Defaults to 6.
periodOptionalNumberTOTP period in seconds. Must be between 10 and 300. Defaults to 30.
algorithmOptionalStringOne of SHA1, SHA256, or SHA512. Defaults to SHA1.
expires_atOptionalStringFuture ISO timestamp after which the saved device expires.

Response ​

json
{
  "id": "9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273",
  "organisation_id": "7f0a9c32-66b2-4e25-a4cf-1f77db8f7f3b",
  "name": "GitHub staging",
  "username": "qa@example.com",
  "issuer": "GitHub",
  "digits": 6,
  "period": 30,
  "algorithm": "SHA1",
  "source": "base32_secret_key",
  "expires_at": null,
  "created_at": "2026-05-18T12:00:00.000Z",
  "updated_at": "2026-05-18T12:00:00.000Z"
}

Create From otpauth URL ​

POST /api/devices/otpauth-url

Use this endpoint when your application or identity provider exposes an otpauth://totp/... setup URL.

sh
curl --request POST \
     --url https://api.mailisk.com/api/devices/otpauth-url \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-Api-Key: {Api Key}' \
     --data '{
       "otp_auth_url": "otpauth://totp/GitHub:qa@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub"
     }'

Values inside the otpauth:// URL are treated as the source of truth for username, issuer, digits, period, and algorithm. The optional body name can override the derived display name.

Body ​

NameRequiredTypeDescription
otp_auth_urlRequiredStringotpauth://totp URL with a Base32 secret query parameter.
nameOptionalStringHuman-readable device name. If omitted, Mailisk derives one.
usernameOptionalStringAccount label used only when missing from the otpauth:// URL.
issuerOptionalStringIssuer used only when missing from the otpauth:// URL.
digitsOptionalNumberOTP length used only when missing from the URL. Supported values are 6 and 8.
periodOptionalNumberTOTP period used only when missing from the URL. Must be between 10 and 300.
algorithmOptionalStringAlgorithm used only when missing from the URL. One of SHA1, SHA256, or SHA512.
expires_atOptionalStringFuture ISO timestamp after which the saved device expires.

Response ​

json
{
  "id": "9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273",
  "organisation_id": "7f0a9c32-66b2-4e25-a4cf-1f77db8f7f3b",
  "name": "GitHub:qa@example.com",
  "username": "qa@example.com",
  "issuer": "GitHub",
  "digits": 6,
  "period": 30,
  "algorithm": "SHA1",
  "source": "otpauth_url",
  "expires_at": null,
  "created_at": "2026-05-18T12:00:00.000Z",
  "updated_at": "2026-05-18T12:00:00.000Z"
}

List Saved Devices ​

GET /api/devices

This endpoint returns active saved authenticator devices for the organisation API key. Expired devices are not returned.

sh
curl --request GET \
     --url 'https://api.mailisk.com/api/devices?limit=5&offset=0&issuer=GitHub' \
     --header 'Accept: application/json' \
     --header 'X-Api-Key: {Api Key}'

Query ​

NameRequiredTypeDescription
limitOptionalNumberMaximum number of devices to return. Must be between 1 and 100.
offsetOptionalNumberNumber of devices to skip for pagination.
usernameOptionalStringCase-insensitive partial username filter.
issuerOptionalStringCase-insensitive partial issuer filter.

Response ​

json
{
  "total_count": 1,
  "options": {
    "limit": 5,
    "offset": 0,
    "issuer": "GitHub"
  },
  "items": [
    {
      "id": "9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273",
      "organisation_id": "7f0a9c32-66b2-4e25-a4cf-1f77db8f7f3b",
      "name": "GitHub staging",
      "username": "qa@example.com",
      "issuer": "GitHub",
      "digits": 6,
      "period": 30,
      "algorithm": "SHA1",
      "source": "custom",
      "expires_at": null,
      "created_at": "2026-05-18T12:00:00.000Z",
      "updated_at": "2026-05-18T12:00:00.000Z"
    }
  ]
}

Get OTP For Saved Device ​

GET /api/devices/{deviceId}/otp

Expired devices cannot generate OTPs and return an error.

Pass min_seconds_until_expire when a test needs a code that will remain valid for at least a minimum number of seconds. If the current code is too close to expiry, Mailisk waits until the next TOTP period starts and returns the next code. The value must be lower than the saved device period.

sh
curl --request GET \
     --url 'https://api.mailisk.com/api/devices/9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273/otp?min_seconds_until_expire=10' \
     --header 'Accept: application/json' \
     --header 'X-Api-Key: {Api Key}'

Path Params ​

NameDescription
deviceIdSaved authenticator device UUID.

Query ​

NameRequiredTypeDescription
min_seconds_until_expireOptionalNumberMinimum seconds the returned code must remain valid. Defaults to 0. Must be less than the saved device TOTP period.

Response ​

json
{
  "code": "123456",
  "expires": "2026-05-18T12:00:30.000Z"
}

Delete Saved Device ​

DELETE /api/devices/{deviceId}

sh
curl --request DELETE \
     --url https://api.mailisk.com/api/devices/9b1f6ec0-b90d-4bd8-8dd0-f6b2d5138273 \
     --header 'X-Api-Key: {Api Key}'

Path Params ​

NameDescription
deviceIdSaved authenticator device UUID.

Successful deletion returns 204 No Content.

Typescript types ​

typescript
interface TotpDeviceListResponse {
  total_count: number;
  options: {
    limit: number;
    offset: number;
    username?: string;
    issuer?: string;
  };
  items: TotpDevice[];
}

interface TotpDevice {
  id: string;
  organisation_id: string;
  name: string;
  username?: string | null;
  issuer?: string | null;
  digits: 6 | 8;
  period: number;
  algorithm: 'SHA1' | 'SHA256' | 'SHA512';
  source: 'shared_secret' | 'custom' | 'base32_secret_key' | 'otpauth_url' | string;
  expires_at?: string | null;
  created_at: string;
  updated_at: string;
}

interface TotpOtpResponse {
  code: string;
  expires: string;
}