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.
Schema
Section titled “Schema”TwoFactorToken
Section titled “TwoFactorToken”{ "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.
List 2FA tokens
Section titled “List 2FA tokens”GET /api/2faRequired scope: 2fa:read
Returns all 2FA tokens owned by the authenticated user.
Response
Section titled “Response”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" }]Create a 2FA token
Section titled “Create a 2FA token”POST /api/2faContent-Type: application/jsonRequired scope: 2fa:write
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
service | string | yes | Service name (e.g. “GitHub”) |
accountName | string | yes | Account identifier (e.g. email) |
secret | string | yes | TOTP secret key (base32 encoded) |
notes | string | no | Optional notes |
{ "service": "GitHub", "accountName": "me@example.com", "secret": "JBSWY3DPEHPK3PXP", "notes": "Personal GitHub account"}The secret is encrypted with AES-256-GCM before being stored.
Response
Section titled “Response”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"}Update a 2FA token
Section titled “Update a 2FA token”PUT /api/2fa/{id}Content-Type: application/jsonRequired scope: 2fa:write
All fields are optional. If secret is provided, it is re-encrypted.
{ "notes": "Updated notes"}Response
Section titled “Response”HTTP/1.1 200 OK
{ "id": "a1b2c3d4-...", "service": "GitHub", "accountName": "me@example.com", "notes": "Updated notes", ...}Delete a 2FA token
Section titled “Delete a 2FA token”DELETE /api/2fa/{id}Required scope: 2fa:write
Response
Section titled “Response”HTTP/1.1 204 No ContentReveal a TOTP code
Section titled “Reveal a TOTP code”POST /api/2fa/{id}/revealRequired scope: 2fa:reveal
Decrypts the stored TOTP secret and generates the current 6-digit code.
Response
Section titled “Response”HTTP/1.1 200 OK
{ "id": "a1b2c3d4-...", "service": "GitHub", "accountName": "me@example.com", "code": "482931", "remainingSeconds": 18}| Field | Description |
|---|---|
code | Current 6-digit TOTP code |
remainingSeconds | Seconds 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.