MailLandDocs

Two-Factor Authentication

Secure your account with TOTP-based two-factor authentication (2FA)

Add an extra layer of security to your MailLand account with TOTP-based two-factor authentication. When enabled, logging in requires both your password and a time-based code from an authenticator app.

Setup 2FA

Initiate setup

POST https://api.mail.land/v1/auth/2fa/setup
Setup 2FA
curl -X POST https://api.mail.land/v1/auth/2fa/setup \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Returns a TOTP secret, QR code, and your backup codes:

2FA setup response
{
  "secret": "JBSWY3DPEHPK3PXP",
  "secretMasked": "JBSW...3PXP",
  "otpAuthUri": "otpauth://totp/MailLand:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=MailLand",
  "qrCodeDataUri": "data:image/png;base64,iVBORw0KGgo...",
  "backupCodes": [
    "A1B2C3D4",
    "E5F6G7H8",
    "I9J0K1L2",
    "M3N4O5P6",
    "Q7R8S9T0",
    "U1V2W3X4",
    "Y5Z6A7B8",
    "C9D0E1F2",
    "G3H4I5J6",
    "K7L8M9N0"
  ]
}
FieldTypeDescription
secretstringBase32-encoded TOTP secret (for manual entry)
secretMaskedstringMasked form of the secret, safe to display in logs/UI
otpAuthUristringURI for authenticator apps
qrCodeDataUristringQR code image as data URI
backupCodesstring[]10 single-use backup codes, 8 characters each (A-Z2-9)

Save the 10 backup codes now: the setup response is the only place they are ever shown. enable only confirms 2FA is on; it does not return the codes again.

Scan the QR code with an authenticator app (Google Authenticator, Authy, 1Password, etc.) or enter the secret manually.

Enable 2FA

Confirm setup by providing a code from your authenticator app:

POST https://api.mail.land/v1/auth/2fa/enable
Enable 2FA request
{
  "code": "123456"
}
Enable 2FA
curl -X POST https://api.mail.land/v1/auth/2fa/enable \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
Enable 2FA response
{
  "message": "Two-factor authentication enabled successfully"
}

The enable response does not repeat the backup codes: they were only shown once, in the setup response above. Each code can be used once to log in if you lose access to your authenticator app.

Verify a Code

When login returns requiresTwoFactor: true, complete authentication by verifying a TOTP code:

POST https://api.mail.land/v1/auth/2fa/verify
2FA verify request
{
  "challengeId": "2fa_challenge_abc123",
  "code": "123456",
  "trustDevice": true
}

trustDevice is optional. When true, the device is remembered for 30 days and 2FA is skipped on subsequent logins from it.

Verify 2FA code
curl -X POST https://api.mail.land/v1/auth/2fa/verify \
  -H "Content-Type: application/json" \
  -d '{
    "challengeId": "2fa_challenge_abc123",
    "code": "123456"
  }'
2FA verify response
{
  "accessToken": "eyJhbGciOiJIUzI1NiIs...",
  "refreshToken": "rt_a1b2c3d4e5...",
  "deviceTrusted": false
}

There is no expiresIn and no user object in this response. deviceTrusted is true when trustDevice was true. The device token itself is never in the body: the API sets it as the HttpOnly __Host-trusted_device cookie, and the browser sends it with the next sign-in.

Use a Backup Code

If you lose access to your authenticator app, use a backup code instead:

POST https://api.mail.land/v1/auth/2fa/verify-backup
Backup code verify request
{
  "challengeId": "2fa_challenge_abc123",
  "code": "a1b2c3d4"
}

The field is code (8–10 characters), not backupCode.

Verify backup code
curl -X POST https://api.mail.land/v1/auth/2fa/verify-backup \
  -H "Content-Type: application/json" \
  -d '{
    "challengeId": "2fa_challenge_abc123",
    "code": "a1b2c3d4"
  }'

Returns the same response as TOTP verification. Each backup code can only be used once.

Regenerate Backup Codes

Generate a new set of backup codes (invalidates all previous codes). Requires a current TOTP code:

POST https://api.mail.land/v1/auth/2fa/backup-codes/regenerate
Regenerate backup codes request
{
  "code": "123456"
}
Regenerate backup codes
curl -X POST https://api.mail.land/v1/auth/2fa/backup-codes/regenerate \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'
Regenerate backup codes response
[
  "X2Y3Z4A5",
  "B6C7D8E9",
  "F2G3H4J5",
  "K6L7M8N9",
  "P2Q3R4S5",
  "T6U7V8W9",
  "X6Y7Z8A9",
  "B2C3D4E5",
  "F6G7H8J9",
  "K2L3M4N5"
]

The response is a bare JSON array of strings, not { "backupCodes": [...] }.

Check 2FA Status

GET https://api.mail.land/v1/auth/2fa/status
Check 2FA status
curl https://api.mail.land/v1/auth/2fa/status \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
2FA status response
{
  "isEnabled": true,
  "enabledAt": "2026-01-15T10:00:00Z",
  "backupCodesRemaining": 6,
  "trustedDevicesCount": 2
}

Trusted Devices

When verifying 2FA, the device can be remembered to skip 2FA on future logins from the same browser.

List Trusted Devices

GET https://api.mail.land/v1/auth/2fa/devices
List trusted devices
curl https://api.mail.land/v1/auth/2fa/devices \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Trusted devices response
[
  {
    "id": "dev_a1b2c3d4e5",
    "deviceName": "Chrome on macOS",
    "ipAddress": "203.0.113.10",
    "createdAt": "2026-01-20T14:00:00Z",
    "expiresAt": "2026-02-19T14:00:00Z",
    "lastUsedAt": "2026-02-17T09:00:00Z",
    "isCurrent": true
  },
  {
    "id": "dev_f6g7h8i9j0",
    "deviceName": "Firefox on Windows",
    "ipAddress": "198.51.100.24",
    "createdAt": "2026-02-01T08:00:00Z",
    "expiresAt": "2026-03-03T08:00:00Z",
    "lastUsedAt": "2026-02-16T15:30:00Z",
    "isCurrent": false
  }
]

The response is a bare array, not { "devices": [...] }, and the fields are deviceName, ipAddress, expiresAt and isCurrent rather than name.

Revoke a Device

DELETE https://api.mail.land/v1/auth/2fa/devices/{id}

Returns 204 on success, or 404 RESOURCE_NOT_FOUND if the device id does not belong to the current user.

Revoke a device
curl -X DELETE https://api.mail.land/v1/auth/2fa/devices/dev_a1b2c3d4e5 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Revoke All Devices

DELETE https://api.mail.land/v1/auth/2fa/devices
Revoke all devices
curl -X DELETE https://api.mail.land/v1/auth/2fa/devices \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

Revokes all trusted devices, requiring 2FA verification on the next login from every device.

Changing the password, resetting it and signing out everywhere (POST /v1/auth/revoke-all-tokens) also revoke every trusted device, in the same way.

Disable 2FA

POST https://api.mail.land/v1/auth/2fa/disable
Disable 2FA request
{
  "code": "123456"
}
Disable 2FA
curl -X POST https://api.mail.land/v1/auth/2fa/disable \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"code": "123456"}'

Requires a valid TOTP or backup code to confirm the action: a TOTP code is tried first, and a backup code is accepted only if the TOTP check fails. Disabling 2FA invalidates all backup codes and trusted devices.

Error Responses

Most failures (an invalid code, an already-enabled/disabled state, an expired challenge, a reused backup code) return 400 INVALID_REQUEST with a message. A malformed request body (wrong code length, missing challengeId, etc.) fails request validation first and returns 400 VALIDATION_FAILED instead. An unknown device returns 404 RESOURCE_NOT_FOUND. There is no separate INVALID_CODE, TWO_FACTOR_* or CHALLENGE_EXPIRED code.

StatusCodeDescription
400INVALID_REQUESTInvalid or expired code, wrong state, reused backup code, etc.
400VALIDATION_FAILEDRequest body failed field validation (e.g. wrong code length, missing field)
404RESOURCE_NOT_FOUNDDevice not found

Next Steps

On this page