Skip to content

Authentication

import { Steps, Aside, Tabs, TabItem } from ‘@astrojs/starlight/components’;

VitaSync uses API key authentication. All requests to the API must include a key in the Authorization header. Keys are workspace-scoped — they can only access data within the workspace they belong to.

Every API key follows this format:

vs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The s_live_ prefix makes keys easy to identify in logs, environment config, and secret scanners. The suffix is 32 cryptographically random characters. The raw key is never stored — VitaSync persists only the SHA-256 hash, so the key cannot be recovered after issuance.

Include your API key as a Bearer token on every request:

http Authorization: Bearer vs_live_abc123...

`ash curl https://api.yourdomain.com/v1/providers \ -H "Authorization: Bearer " ` ` s const res = await fetch('https://api.yourdomain.com/v1/providers', { headers: { Authorization: Bearer , }, }); ` `python import httpx
client = httpx.Client(base_url='https://api.yourdomain.com')
resp = client.get('/v1/providers', headers={
'Authorization': f'Bearer {API_KEY}'
})
`

Each API key is issued with one or more scopes that define what it is allowed to do. Use the minimum scope necessary for each key.

ScopeWhat it allows
eadQuery users, health data, events, personal records, connections, providers
writeCreate/update users, trigger syncs, disconnect connections
dminCreate/delete API keys, manage webhooks, delete users and health data (GDPR), rotate keys

On first startup, VitaSync auto-creates a workspace and a bootstrap API key from the values in .env:

ash ADMIN_WORKSPACE_SLUG=my-workspace ADMIN_API_KEY=vs_live_changeme_for_production

Use this key to create purpose-specific keys, then revoke it before going to production.

Requires dmin scope.

ash curl -X POST https://api.yourdomain.com/v1/api-keys \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "backend-service", "scopes": ["read", "write"], "expiresAt": "2027-01-01T00:00:00.000Z" }'

Request body:

FieldTypeRequiredDescription
amestringYesHuman-readable label for the key
scopes(“read” | “write” | “admin”)[]YesPermission scopes
xpiresAtISO 8601 stringNoOptional expiry date — keys are permanent by default

Response (201 Created):

json { "id": "01JA4MNPQR8STUVWXYZ12345", "name": "backend-service", "keyPrefix": "vs_live_", "scopes": ["read", "write"], "expiresAt": "2027-01-01T00:00:00.000Z", "createdAt": "2025-06-01T12:00:00.000Z", "rawKey": "vs_live_abc123xyz789..." }

ash curl https://api.yourdomain.com/v1/api-keys \ -H "Authorization: Bearer "

Response (200 OK):

json [ { "id": "01JA4MNPQR8STUVWXYZ12345", "name": "backend-service", "keyPrefix": "vs_live_ab", "scopes": ["read", "write"], "expiresAt": "2027-01-01T00:00:00.000Z", "lastUsedAt": "2025-06-10T09:31:00.000Z", "createdAt": "2025-06-01T12:00:00.000Z" } ]

The keyPrefix field (first 8 characters) is included so you can identify which key is which without storing the full key.

Rotation invalidates the old key and issues a new one atomically. Requires dmin scope.

ash curl -X POST https://api.yourdomain.com/v1/api-keys/01JA4MNPQR8STUVWXYZ12345/rotate \ -H "Authorization: Bearer "

Response (200 OK):

json { "id": "01JA4MNPQR8STUVWXYZ99999", "name": "backend-service", "keyPrefix": "vs_live_de", "scopes": ["read", "write"], "expiresAt": "2027-01-01T00:00:00.000Z", "createdAt": "2025-06-15T08:00:00.000Z", "rawKey": "vs_live_def456uvw..." }

Immediately revokes the key — in-flight requests using the key will fail with 401. Requires dmin scope.

`ash curl -X DELETE https://api.yourdomain.com/v1/api-keys/01JA4MNPQR8STUVWXYZ12345
-H “Authorization: Bearer ”

`

StatusCodeMeaning
401 UnauthorizedUNAUTHORIZEDNo Authorization header, malformed header, or key not found
403 ForbiddenFORBIDDENKey is valid but lacks the required scope for this endpoint
429 Too Many RequestsRATE_LIMIT_EXCEEDEDRequest rate exceeded; see Retry-After header

401 response: json { "statusCode": 401, "error": "Unauthorized", "message": "Invalid or missing API key" }

403 response: json { "statusCode": 403, "error": "Forbidden", "message": "Insufficient scope. Required: admin" }

SettingDefault
Max requests per window100
Window duration60 seconds

Configure via RATE_LIMIT_MAX and RATE_LIMIT_WINDOW_MS in .env. When the limit is exceeded, the API returns 429 with a Retry-After header indicating when to retry.

  • One key per service — never share keys between applications. This limits blast radius and enables independent rotation.
  • Minimum scopes — a background sync worker only needs ead + write, never dmin.
  • Set expiry dates — short-lived keys reduce exposure. Pair with automated rotation in CI/CD.
  • Rotate on compromise — if a key leaks, rotate immediately. VitaSync invalidates the old key the moment rotation completes.
  • Never log raw keys — the keyPrefix field is explicitly provided for log correlation. Use it instead.