MFA API
REST API endpoints for enrolling, verifying, and managing multi-factor authentication factors.
MFA API
The MFA API lets you enroll new factors, verify challenges, and manage existing factors for a user.
Factor Object
{
"id": "fac_01HABCDEF555444",
"user_id": "usr_01HABCDEF123456",
"type": "totp",
"name": "Authenticator App",
"status": "active",
"created_at": "2024-01-10T09:00:00Z",
"last_used_at": "2024-01-20T08:15:00Z"
}Factor types: totp, sms, email_otp, webauthn, backup_codes
Factor status: pending (enrolled but not yet verified), active, disabled
GET /v1/users/:user_id/mfa/factors
List all MFA factors for a user.
curl "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/factors" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"Response:
{
"data": [
{
"id": "fac_01HABCDEF555444",
"type": "totp",
"name": "Authenticator App",
"status": "active",
"created_at": "2024-01-10T09:00:00Z"
}
],
"total": 1
}POST /v1/users/:user_id/mfa/factors
Begin enrolling a new MFA factor. Returns enrollment data (QR code URI for TOTP, etc.).
Body:
| Field | Type | Required | Description |
|---|---|---|---|
type | string | ✅ | totp, sms, email_otp, webauthn |
name | string | — | Human-readable label |
phone_number | string | — | Required when type is sms |
curl -X POST "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/factors" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"type": "totp", "name": "Authenticator App"}'Response for TOTP:
{
"factor_id": "fac_01HABCDEF555444",
"type": "totp",
"status": "pending",
"totp": {
"secret": "JBSWY3DPEHPK3PXP",
"uri": "otpauth://totp/HelloJohn:alice%40example.com?secret=JBSWY3DPEHPK3PXP&issuer=HelloJohn",
"qr_code": "data:image/png;base64,..."
}
}Response for SMS:
{
"factor_id": "fac_01HABCDEF555445",
"type": "sms",
"status": "pending",
"phone_number": "+1***5678"
}After receiving this response, prompt the user to enter the OTP they received and call the confirm endpoint.
POST /v1/users/:user_id/mfa/factors/:factor_id/confirm
Confirm a pending factor by verifying the first OTP. Activates the factor.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
code | string | ✅ | OTP or TOTP code from the factor |
curl -X POST "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/factors/fac_01HABCDEF555444/confirm" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"code": "123456"}'Response: 200 OK — Returns activated factor object.
DELETE /v1/users/:user_id/mfa/factors/:factor_id
Remove an MFA factor. If the factor is the user's only active factor and the tenant requires MFA, this returns a 403 error.
curl -X DELETE "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/factors/fac_01HABCDEF555444" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"Response: 204 No Content
POST /v1/mfa/challenge
Create an MFA challenge during authentication. Called server-side when the sign-in response indicates mfa_required.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
factor_id | string | ✅ | The factor to challenge |
challenge_id | string | ✅ | Challenge ID from the mfa_required error |
curl -X POST https://api.hellojohn.dev/v1/mfa/challenge \
-H "Authorization: Bearer pk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{
"factor_id": "fac_01HABCDEF555444",
"challenge_id": "chl_01HABCDEF888777"
}'For SMS and email OTP factors, this triggers sending the one-time code.
Response: 200 OK
{
"challenge_id": "chl_01HABCDEF888777",
"factor_type": "sms",
"expires_at": "2024-01-15T10:35:00Z"
}POST /v1/mfa/verify
Verify an MFA challenge. On success, the user's session is promoted to MFA-verified.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
challenge_id | string | ✅ | Challenge ID |
factor_id | string | ✅ | Factor ID being verified |
code | string | ✅ | OTP or TOTP code |
curl -X POST https://api.hellojohn.dev/v1/mfa/verify \
-H "Authorization: Bearer pk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{
"challenge_id": "chl_01HABCDEF888777",
"factor_id": "fac_01HABCDEF555444",
"code": "123456"
}'Response: 200 OK — Returns new access token with mfa_verified: true.
{
"access_token": "eyJhbGciOiJFZERTQSJ9...",
"refresh_token": "rt_01HABCDEF...",
"expires_in": 3600
}POST /v1/mfa/backup-codes/generate
Generate a new set of backup codes for a user. This invalidates any previously generated codes.
curl -X POST "https://api.hellojohn.dev/v1/users/usr_01HABCDEF123456/mfa/backup-codes/generate" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"Response:
{
"codes": [
"ABCD-EFGH",
"IJKL-MNOP",
"QRST-UVWX",
"YZAB-CDEF",
"GHIJ-KLMN",
"OPQR-STUV",
"WXYZ-0123",
"4567-89AB"
],
"generated_at": "2024-01-15T10:00:00Z"
}Store these codes securely. They are shown only once.