API design document for the E2E encrypted TOTP vault system.
This document describes the REST API design for 2FAir's end-to-end encrypted TOTP vault. The API is designed to support WebAuthn authentication, encrypted data synchronization, and zero-knowledge architecture while maintaining security and performance.
- Zero-Knowledge: Server never receives plaintext TOTP seeds
- WebAuthn Only: No password-based authentication
- E2E Encryption: All sensitive data encrypted client-side
- Rate Limiting: Protection against abuse and attacks
- Audit Logging: Comprehensive security event logging
- Resource-Oriented: Clear resource hierarchy and naming
- HTTP Methods: Proper use of GET, POST, PUT, DELETE
- Status Codes: Meaningful HTTP status codes
- Consistent Format: Standardized request/response structure
- Pagination: Cursor-based pagination for large datasets
- Stateless: No server-side session state
- Cacheable: Appropriate cache headers
- Efficient Sync: Delta-based synchronization
- Compression: gzip/brotli compression support
Production: https://api.2fair.dev/v1
Development: https://api-dev.2fair.dev/v1
Local: http://localhost:8080/v1
- Request:
application/jsonwith UTF-8 encoding - Response:
application/jsonwith UTF-8 encoding - Binary Data: Base64 encoded within JSON
All authenticated endpoints require a valid WebAuthn session token:
Authorization: Bearer <jwt_token>
X-Device-ID: <stable_device_identifier>Begin WebAuthn registration process.
Request:
{
"username": "user@example.com",
"displayName": "John Doe"
}Response:
{
"success": true,
"challengeId": "challenge-uuid",
"publicKeyCredentialCreationOptions": {
"challenge": "base64-encoded-challenge",
"rp": {
"name": "2FAir",
"id": "2fair.dev"
},
"user": {
"id": "base64-encoded-user-id",
"name": "user@example.com",
"displayName": "John Doe"
},
"pubKeyCredParams": [
{"alg": -7, "type": "public-key"},
{"alg": -257, "type": "public-key"}
],
"authenticatorSelection": {
"userVerification": "required",
"residentKey": "preferred"
},
"extensions": {
"prf": {}
}
}
}Complete WebAuthn registration.
Request:
{
"challengeId": "challenge-uuid",
"credential": {
"id": "credential-id",
"rawId": "base64-raw-id",
"response": {
"clientDataJSON": "base64-client-data",
"attestationObject": "base64-attestation"
},
"getClientExtensionResults": {
"prf": true
}
},
"initialEncryptionKey": {
"wrappedDEK": "base64-wrapped-dek",
"salt": "base64-salt"
}
}Response:
{
"success": true,
"user": {
"id": "user-uuid",
"username": "user@example.com",
"displayName": "John Doe",
"createdAt": "2025-01-07T10:00:00Z"
},
"token": "jwt-token",
"expiresAt": "2025-01-07T11:00:00Z"
}Begin WebAuthn authentication.
Request:
{
"username": "user@example.com"
}Response:
{
"success": true,
"challengeId": "challenge-uuid",
"publicKeyCredentialRequestOptions": {
"challenge": "base64-challenge",
"allowCredentials": [
{
"id": "base64-credential-id",
"type": "public-key"
}
],
"userVerification": "required",
"extensions": {
"prf": {
"eval": {
"first": "base64-prf-input"
}
}
}
}
}Complete WebAuthn authentication.
Request:
{
"challengeId": "challenge-uuid",
"credential": {
"id": "credential-id",
"rawId": "base64-raw-id",
"response": {
"clientDataJSON": "base64-client-data",
"authenticatorData": "base64-authenticator-data",
"signature": "base64-signature"
},
"getClientExtensionResults": {
"prf": {
"results": {
"first": "base64-prf-output"
}
}
}
},
"deviceInfo": {
"deviceId": "stable-device-id",
"deviceName": "iPhone 15 Pro",
"deviceType": "mobile",
"userAgent": "Mozilla/5.0..."
}
}Response:
{
"success": true,
"user": {
"id": "user-uuid",
"username": "user@example.com",
"displayName": "John Doe"
},
"encryptionKey": {
"wrappedDEK": "base64-wrapped-dek",
"salt": "base64-salt",
"version": 1
},
"token": "jwt-token",
"expiresAt": "2025-01-07T11:00:00Z"
}Refresh authentication token.
Headers:
Authorization: Bearer <current_token>Response:
{
"success": true,
"token": "new-jwt-token",
"expiresAt": "2025-01-07T12:00:00Z"
}Logout and invalidate session.
Headers:
Authorization: Bearer <jwt_token>
X-Device-ID: <device_id>Response:
{
"success": true,
"message": "Logged out successfully"
}Get encrypted TOTP seeds for user.
Headers:
Authorization: Bearer <jwt_token>
X-Device-ID: <device_id>Query Parameters:
since(optional): RFC3339 timestamp for delta synclimit(optional): Maximum number of results (default: 100)cursor(optional): Pagination cursor
Response:
{
"success": true,
"seeds": [
{
"id": "seed-uuid",
"issuer": "Google",
"accountName": "user@gmail.com",
"iconUrl": "https://cdn.2fair.dev/icons/google.png",
"tags": ["work", "email"],
"encryptedData": {
"ciphertext": "base64-ciphertext",
"iv": "base64-iv",
"authTag": "base64-auth-tag"
},
"keyVersion": 1,
"createdAt": "2025-01-07T10:00:00Z",
"updatedAt": "2025-01-07T10:00:00Z"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
},
"syncTimestamp": "2025-01-07T10:30:00Z"
}Create new encrypted TOTP seed.
Request:
{
"issuer": "GitHub",
"accountName": "user@example.com",
"iconUrl": "https://cdn.2fair.dev/icons/github.png",
"tags": ["work", "development"],
"encryptedData": {
"ciphertext": "base64-ciphertext",
"iv": "base64-iv",
"authTag": "base64-auth-tag"
}
}Response:
{
"success": true,
"seed": {
"id": "seed-uuid",
"issuer": "GitHub",
"accountName": "user@example.com",
"iconUrl": "https://cdn.2fair.dev/icons/github.png",
"tags": ["work", "development"],
"encryptedData": {
"ciphertext": "base64-ciphertext",
"iv": "base64-iv",
"authTag": "base64-auth-tag"
},
"keyVersion": 1,
"createdAt": "2025-01-07T10:00:00Z",
"updatedAt": "2025-01-07T10:00:00Z"
}
}Update existing encrypted TOTP seed.
Request:
{
"issuer": "GitHub Enterprise",
"accountName": "user@company.com",
"iconUrl": "https://cdn.2fair.dev/icons/github.png",
"tags": ["work", "enterprise"],
"encryptedData": {
"ciphertext": "base64-new-ciphertext",
"iv": "base64-new-iv",
"authTag": "base64-new-auth-tag"
}
}Response:
{
"success": true,
"seed": {
"id": "seed-uuid",
"issuer": "GitHub Enterprise",
"accountName": "user@company.com",
"iconUrl": "https://cdn.2fair.dev/icons/github.png",
"tags": ["work", "enterprise"],
"encryptedData": {
"ciphertext": "base64-new-ciphertext",
"iv": "base64-new-iv",
"authTag": "base64-new-auth-tag"
},
"keyVersion": 1,
"createdAt": "2025-01-07T10:00:00Z",
"updatedAt": "2025-01-07T10:30:00Z"
}
}Delete TOTP seed.
Response:
{
"success": true,
"message": "TOTP seed deleted successfully"
}Search TOTP seeds by metadata.
Query Parameters:
q: Search querylimit(optional): Maximum results (default: 50)cursor(optional): Pagination cursor
Response:
{
"success": true,
"query": "google",
"seeds": [
{
"id": "seed-uuid",
"issuer": "Google",
"accountName": "user@gmail.com",
"iconUrl": "https://cdn.2fair.dev/icons/google.png",
"tags": ["personal", "email"],
"encryptedData": {
"ciphertext": "base64-ciphertext",
"iv": "base64-iv",
"authTag": "base64-auth-tag"
},
"keyVersion": 1,
"createdAt": "2025-01-07T10:00:00Z",
"updatedAt": "2025-01-07T10:00:00Z"
}
],
"pagination": {
"hasMore": false,
"nextCursor": null
}
}Get user's registered devices.
Response:
{
"success": true,
"devices": [
{
"id": "device-session-uuid",
"deviceId": "stable-device-id",
"deviceName": "iPhone 15 Pro",
"deviceType": "mobile",
"lastSyncAt": "2025-01-07T10:30:00Z",
"createdAt": "2025-01-07T09:00:00Z",
"expiresAt": "2025-01-14T09:00:00Z",
"isActive": true,
"isCurrent": true
}
]
}Register new device for sync.
Request:
{
"deviceId": "new-device-id",
"deviceName": "MacBook Pro",
"deviceType": "desktop",
"expirationHours": 168
}Response:
{
"success": true,
"device": {
"id": "device-session-uuid",
"deviceId": "new-device-id",
"deviceName": "MacBook Pro",
"deviceType": "desktop",
"lastSyncAt": "2025-01-07T10:30:00Z",
"createdAt": "2025-01-07T10:30:00Z",
"expiresAt": "2025-01-14T10:30:00Z",
"isActive": true
}
}Revoke device access.
Response:
{
"success": true,
"message": "Device access revoked successfully"
}Get sync operations since timestamp.
Query Parameters:
since: Timestamp vector for last synclimit(optional): Maximum operations (default: 100)
Response:
{
"success": true,
"operations": [
{
"id": "operation-uuid",
"operationType": "create",
"resourceType": "totp_seed",
"resourceId": "seed-uuid",
"timestampVector": 1704628800000,
"deviceId": "source-device-id",
"deviceName": "iPhone 15 Pro",
"createdAt": "2025-01-07T10:00:00Z"
}
],
"latestTimestamp": 1704628800000
}Record sync operation.
Request:
{
"operationType": "update",
"resourceType": "totp_seed",
"resourceId": "seed-uuid",
"timestampVector": 1704628800001
}Response:
{
"success": true,
"operation": {
"id": "operation-uuid",
"operationType": "update",
"resourceType": "totp_seed",
"resourceId": "seed-uuid",
"timestampVector": 1704628800001,
"deviceId": "current-device-id",
"createdAt": "2025-01-07T10:01:00Z"
}
}Create encrypted backup recovery code.
Request:
{
"encryptedBlob": "base64-encrypted-backup-blob",
"salt": "base64-pbkdf2-salt",
"hint": "My secure passphrase hint"
}Response:
{
"success": true,
"recoveryCode": {
"id": "recovery-uuid",
"hint": "My secure passphrase hint",
"createdAt": "2025-01-07T10:00:00Z"
}
}Recover account using backup code.
Request:
{
"username": "user@example.com",
"encryptedBlob": "base64-encrypted-backup-blob",
"passphrase": "user-recovery-passphrase"
}Response:
{
"success": true,
"recoveredData": {
"wrappedDEK": "base64-wrapped-dek",
"salt": "base64-original-salt",
"keyVersion": 1
}
}Get user profile information.
Response:
{
"success": true,
"user": {
"id": "user-uuid",
"username": "user@example.com",
"displayName": "John Doe",
"createdAt": "2025-01-07T09:00:00Z",
"lastLoginAt": "2025-01-07T10:00:00Z"
},
"stats": {
"totalSeeds": 15,
"activeDevices": 3,
"lastSyncAt": "2025-01-07T10:30:00Z"
}
}Update user profile.
Request:
{
"displayName": "John Smith",
"username": "john.smith@example.com"
}Response:
{
"success": true,
"user": {
"id": "user-uuid",
"username": "john.smith@example.com",
"displayName": "John Smith",
"createdAt": "2025-01-07T09:00:00Z",
"updatedAt": "2025-01-07T10:30:00Z"
}
}Delete user account (with confirmation).
Request:
{
"confirmDeletion": "DELETE_MY_ACCOUNT",
"reason": "No longer needed"
}Response:
{
"success": true,
"message": "Account deleted successfully"
}All API errors follow a consistent format:
{
"success": false,
"error": {
"code": "WEBAUTHN_CHALLENGE_EXPIRED",
"message": "WebAuthn challenge has expired. Please try again.",
"details": {
"challengeId": "challenge-uuid",
"expiredAt": "2025-01-07T10:05:00Z"
}
},
"requestId": "req-uuid-for-debugging"
}UNAUTHORIZED: Missing or invalid authenticationWEBAUTHN_CHALLENGE_EXPIRED: Challenge token expiredWEBAUTHN_VERIFICATION_FAILED: Credential verification failedPRF_NOT_SUPPORTED: WebAuthn PRF extension not availableDEVICE_NOT_REGISTERED: Device not registered for sync
INVALID_REQUEST: Malformed request bodyVALIDATION_ERROR: Request validation failedRESOURCE_NOT_FOUND: Requested resource doesn't existDUPLICATE_RESOURCE: Resource already existsRATE_LIMIT_EXCEEDED: Too many requests
INTERNAL_ERROR: Unexpected server errorDATABASE_ERROR: Database operation failedENCRYPTION_ERROR: Cryptographic operation failedSYNC_CONFLICT: Unable to resolve sync conflict
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704628800| Category | Limit | Window |
|---|---|---|
| Authentication | 10 requests | 1 minute |
| Vault Operations | 100 requests | 1 minute |
| Sync Operations | 50 requests | 1 minute |
| Search | 20 requests | 1 minute |
| User Management | 10 requests | 1 minute |
Content-Security-Policy: default-src 'self'; script-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
Referrer-Policy: strict-origin-when-cross-originFor large datasets, use cursor-based pagination:
Request:
GET /vault/seeds?limit=50&cursor=eyJpZCI6InNlZWQtdXVpZCJ9Response:
{
"success": true,
"seeds": [...],
"pagination": {
"hasMore": true,
"nextCursor": "eyJpZCI6Im5leHQtc2VlZC11dWlkIn0",
"prevCursor": "eyJpZCI6InByZXYtc2VlZC11dWlkIn0"
}
}- URL Versioning:
/v1/,/v2/etc. - Backward Compatibility: Previous versions supported for 12 months
- Deprecation Notice: 90-day advance notice via headers
- Migration Guide: Comprehensive migration documentation
API-Version: 1.0
API-Deprecation-Notice: Version 1.0 will be deprecated on 2025-12-31
API-Sunset: 2026-01-31This API design provides a secure, scalable foundation for the E2E encrypted TOTP vault while maintaining excellent developer experience and security standards.