Skip to content

Two-Factor Tokens

Two-factor tokens store encrypted TOTP secrets. The stored secret is never exposed in list or detail responses — use the /reveal endpoint to generate a live TOTP code.

{
"id": "a1b2c3d4-1234-5678-abcd-ef0123456789",
"userId": "user_abc123",
"service": "GitHub",
"accountName": "me@example.com",
"notes": "Personal GitHub account",
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z"
}

The encryptedSecret field is never included in responses.


GET /api/2fa

Required scope: 2fa:read

Returns all 2FA tokens owned by the authenticated user.

HTTP/1.1 200 OK
[
{
"id": "a1b2c3d4-...",
"userId": "user_abc123",
"service": "GitHub",
"accountName": "me@example.com",
"notes": "Personal GitHub account",
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z"
}
]

POST /api/2fa
Content-Type: application/json

Required scope: 2fa:write

FieldTypeRequiredDescription
servicestringyesService name (e.g. “GitHub”)
accountNamestringyesAccount identifier (e.g. email)
secretstringyesTOTP secret key (base32 encoded)
notesstringnoOptional notes
{
"service": "GitHub",
"accountName": "me@example.com",
"secret": "JBSWY3DPEHPK3PXP",
"notes": "Personal GitHub account"
}

The secret is encrypted with AES-256-GCM before being stored.

HTTP/1.1 201 Created
{
"id": "a1b2c3d4-...",
"userId": "user_abc123",
"service": "GitHub",
"accountName": "me@example.com",
"notes": "Personal GitHub account",
"createdAt": "2026-01-15T10:00:00.000Z",
"updatedAt": "2026-01-15T10:00:00.000Z"
}

PUT /api/2fa/{id}
Content-Type: application/json

Required scope: 2fa:write

All fields are optional. If secret is provided, it is re-encrypted.

{
"notes": "Updated notes"
}
HTTP/1.1 200 OK
{
"id": "a1b2c3d4-...",
"service": "GitHub",
"accountName": "me@example.com",
"notes": "Updated notes",
...
}

DELETE /api/2fa/{id}

Required scope: 2fa:write

HTTP/1.1 204 No Content

POST /api/2fa/{id}/reveal

Required scope: 2fa:reveal

Decrypts the stored TOTP secret and generates the current 6-digit code.

HTTP/1.1 200 OK
{
"id": "a1b2c3d4-...",
"service": "GitHub",
"accountName": "me@example.com",
"code": "482931",
"remainingSeconds": 18
}
FieldDescription
codeCurrent 6-digit TOTP code
remainingSecondsSeconds until the code expires (TOTP period is 30 seconds)

Returns 404 if the token does not exist, is not owned by the authenticated user, or cannot be decrypted.