API Reference
Base URLs
| Environment | URL |
|---|---|
| Local development | http://localhost:3001/api/v1 |
| Production | https://api.handoverkey.app/api/v1 |
Authentication
HandoverKey supports two auth patterns:
- Browser clients — httpOnly cookies set by the API (
accessToken,refreshToken) - Programmatic clients —
Authorization: Bearer <token>header
Every protected endpoint validates both the JWT and the backing server-side session record.
Cookie details
| Cookie | Scope | Lifetime |
|---|---|---|
accessToken | All protected routes | 1 hour |
refreshToken | /api/v1/auth/refresh only | 7 days |
Production cookies are Secure and SameSite=None.
Auth flow
POST /auth/registerGET /auth/verify-email?token=...POST /auth/login→ API sets cookies and returns user payloadGET /auth/profile→ hydrate session state in the browser
If TOTP is enabled, login accepts twoFactorCode or recoveryCode.
Response conventions
| Status | Meaning |
|---|---|
200 / 201 | Success |
400 | Validation error |
401 | Missing or invalid auth |
403 | Authenticated but not authorized (e.g. tier limit) |
404 | Resource not found |
429 | Rate limited |
Endpoints
Auth
Public
POST /auth/registerPOST /auth/loginPOST /auth/forgot-passwordPOST /auth/reset-passwordGET /auth/verify-email?token=POST /auth/resend-verificationPOST /auth/refreshAuthenticated
POST /auth/logoutGET /auth/profilePUT /auth/profilePUT /auth/change-passwordPOST /auth/2fa/setupPOST /auth/2fa/enablePOST /auth/2fa/disableDELETE /auth/delete-accountLogin request
{ "email": "user@example.com", "password": "<pbkdf2-derived-auth-key>", "twoFactorCode": "123456"}Login response
{ "message": "Login successful", "user": { "id": "user-id", "email": "user@example.com", "name": "Alice", "twoFactorEnabled": true, "salt": "base64-salt" }}Vault
Authenticated
POST /vault/entriesGET /vault/entriesGET /vault/entries/:idPUT /vault/entries/:idDELETE /vault/entries/:idGET /vault/exportPOST /vault/importPublic
GET /vault/successor-access?token=Import supports merge (default) and replace modes. All payloads are pre-encrypted by the client — the server never receives plaintext vault content.
Import request
{ "mode": "merge", "entries": [ { "encryptedData": "base64-ciphertext", "iv": "base64-iv", "salt": "base64-salt", "algorithm": "AES-GCM", "category": "finance", "tags": [] } ]}Activity and check-in
Authenticated
GET /activityPOST /activity/check-inPublic
GET /activity/check-in-link?token=POST /activity/check-in-linkInactivity settings
Authenticated
GET /inactivity/settingsPUT /inactivity/settingsPOST /inactivity/pausePOST /inactivity/resumeSessions
Authenticated
GET /sessionsDELETE /sessions/:sessionIdPOST /sessions/invalidate-othersSuccessors
Public
GET /successors/verify?token=Authenticated
PUT /successors/sharesPOST /successorsGET /successorsGET /successors/:idPUT /successors/:idDELETE /successors/:idPOST /successors/:id/resend-verificationPOST /successors/:id/verifyGET /successors/:id/assigned-entriesPUT /successors/:id/assigned-entriesHandover
Public
POST /handover/respondAuthenticated
GET /handover/statusPOST /handover/cancelBilling
Authenticated
GET /billing/statusPOST /billing/checkoutPOST /billing/portalPublic (Stripe webhook)
POST /billing/webhookAdmin
Authenticated admin-only
GET /admin/dashboardGET /admin/usersPOST /admin/users/:userId/unlockGET /admin/users/:userId/lockout-statusAdmin access is controlled by the ADMIN_EMAILS env var allowlist.
Operational
Public
GET /healthGET /metrics/health returns status for database, Redis, job queues, and the realtime service.
Realtime (WebSocket)
Connect to /ws with valid auth cookies. The server pushes:
- Inactivity reminder notifications
- Handover state change events
Local: ws://localhost:3001/ws
Production: wss://api.handoverkey.app/ws