Skip to content

API Reference

Base URLs

EnvironmentURL
Local developmenthttp://localhost:3001/api/v1
Productionhttps://api.handoverkey.app/api/v1

Authentication

HandoverKey supports two auth patterns:

  • Browser clients — httpOnly cookies set by the API (accessToken, refreshToken)
  • Programmatic clientsAuthorization: Bearer <token> header

Every protected endpoint validates both the JWT and the backing server-side session record.

CookieScopeLifetime
accessTokenAll protected routes1 hour
refreshToken/api/v1/auth/refresh only7 days

Production cookies are Secure and SameSite=None.

Auth flow

  1. POST /auth/register
  2. GET /auth/verify-email?token=...
  3. POST /auth/login → API sets cookies and returns user payload
  4. GET /auth/profile → hydrate session state in the browser

If TOTP is enabled, login accepts twoFactorCode or recoveryCode.

Response conventions

StatusMeaning
200 / 201Success
400Validation error
401Missing or invalid auth
403Authenticated but not authorized (e.g. tier limit)
404Resource not found
429Rate limited

Endpoints

Auth

Public

POST /auth/register
POST /auth/login
POST /auth/forgot-password
POST /auth/reset-password
GET /auth/verify-email?token=
POST /auth/resend-verification
POST /auth/refresh

Authenticated

POST /auth/logout
GET /auth/profile
PUT /auth/profile
PUT /auth/change-password
POST /auth/2fa/setup
POST /auth/2fa/enable
POST /auth/2fa/disable
DELETE /auth/delete-account

Login 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/entries
GET /vault/entries
GET /vault/entries/:id
PUT /vault/entries/:id
DELETE /vault/entries/:id
GET /vault/export
POST /vault/import

Public

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 /activity
POST /activity/check-in

Public

GET /activity/check-in-link?token=
POST /activity/check-in-link

Inactivity settings

Authenticated

GET /inactivity/settings
PUT /inactivity/settings
POST /inactivity/pause
POST /inactivity/resume

Sessions

Authenticated

GET /sessions
DELETE /sessions/:sessionId
POST /sessions/invalidate-others

Successors

Public

GET /successors/verify?token=

Authenticated

PUT /successors/shares
POST /successors
GET /successors
GET /successors/:id
PUT /successors/:id
DELETE /successors/:id
POST /successors/:id/resend-verification
POST /successors/:id/verify
GET /successors/:id/assigned-entries
PUT /successors/:id/assigned-entries

Handover

Public

POST /handover/respond

Authenticated

GET /handover/status
POST /handover/cancel

Billing

Authenticated

GET /billing/status
POST /billing/checkout
POST /billing/portal

Public (Stripe webhook)

POST /billing/webhook

Admin

Authenticated admin-only

GET /admin/dashboard
GET /admin/users
POST /admin/users/:userId/unlock
GET /admin/users/:userId/lockout-status

Admin access is controlled by the ADMIN_EMAILS env var allowlist.


Operational

Public

GET /health
GET /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