Skip to content

Connect a User

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

VitaSync connection model has three actors: your backend (holds the API key), a user (owns the wearable device), and the wearable provider (Fitbit, Garmin, etc.). This guide walks through the complete flow from creating a user to querying their health data.

Your Backend VitaSync API Provider (e.g. Fitbit)
| | |
|-- POST /v1/users -------->| |
|<-- { userId } ------------| |
| | |
|-- GET /v1/oauth/:p/authorize?userId= --> |
|<-- 302 redirect to provider login |
| | |
| user logs in to provider |
| | |
| GET /v1/oauth/:p/callback |
| |<-- authorization code ----|
| |-- exchange code ----------|
| |<-- access + refresh token |
| |-- stores encrypted tokens |
|<-- redirect to your app w/ connectionId |
| | |
|-- POST .../sync --------->| |
|<-- 202 Accepted (async) --| |

VitaSync users are identified by an externalId — typically the user ID from your own system. If a user with that externalId already exists, VitaSync returns the existing record (upsert semantics).

Requires write scope.

Terminal window
curl -X POST https://api.yourdomain.com/v1/users \
-H "Authorization: Bearer $VITASYNC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalId": "usr_abc123",
"email": "alice@example.com",
"displayName": "Alice"
}'

Request body:

FieldTypeRequiredDescription
externalIdstringYesYour internal user ID — unique per workspace
emailstringNoUser email address
displayNamestringNoHuman-readable name
metadataobjectNoArbitrary JSON — stored as-is and returned on all user reads

Response (200 OK if existing, 201 Created if new):

{
"id": "01JA4MNPQR8STUVWXYZ00001",
"externalId": "usr_abc123",
"email": "alice@example.com",
"displayName": "Alice",
"metadata": {},
"createdAt": "2025-06-01T10:00:00.000Z",
"updatedAt": "2025-06-01T10:00:00.000Z"
}

Save the VitaSync id — use it for all subsequent requests for this user.

To connect a provider, redirect your user’s browser to the VitaSync authorization URL. VitaSync handles the OAuth dance with the provider and redirects back to your app when done.

GET /v1/oauth/{providerId}/authorize?userId={userId}

Parameters:

ParameterDescription
providerIdOne of: fitbit, garmin, whoop, strava
userIdThe VitaSync user ID from Step 1

Example button:

function ConnectFitbitButton({ vitasyncUserId }: { vitasyncUserId: string }) {
const authorizeUrl =
`https://api.yourdomain.com/v1/oauth/fitbit/authorize?userId=${vitasyncUserId}`;
return <a href={authorizeUrl}>Connect Fitbit</a>;
}

What happens next:

  1. VitaSync redirects the user to the provider’s login and consent screen.
  2. The user grants permission.
  3. The provider redirects back to GET /v1/oauth/:providerId/callback.
  4. VitaSync exchanges the code for tokens, encrypts with AES-256-GCM, and stores them.
  5. VitaSync redirects the user to your OAUTH_CALLBACK_URL with connectionId and providerId as query params.

Configure your redirect destination in .env:

Terminal window
OAUTH_CALLBACK_URL=https://app.yourdomain.com/settings/connections/callback

Your callback page receives the connection details as query parameters:

https://app.yourdomain.com/settings/connections/callback
?connectionId=01JA4MNPQR8STUVWXYZ00002
&providerId=fitbit

Store the connectionId alongside the user in your database — you will use it to trigger syncs and disconnect the provider.

After connecting, trigger an initial sync to pull historical data. The sync runs asynchronously — VitaSync enqueues a background job and returns immediately.

Requires write scope.

Terminal window
curl -X POST \
"https://api.yourdomain.com/v1/users/01JA4MNPQR8STUVWXYZ00001/connections/01JA4MNPQR8STUVWXYZ00002/sync" \
-H "Authorization: Bearer $VITASYNC_API_KEY"

Response (202 Accepted):

{
"jobId": "sync-job-01JA4MNPQR...",
"status": "queued"
}

The initial sync pulls the last 30 days of data. Subsequent syncs pull data from lastSyncedAt forward.

Once the sync completes (a few seconds to a few minutes depending on the provider and data volume):

Terminal window
curl "https://api.yourdomain.com/v1/users/01JA4MNPQR8STUVWXYZ00001/health?metricType=steps&limit=7" \
-H "Authorization: Bearer $VITASYNC_API_KEY"

See Query Health Data for the full query API including timeseries, daily summaries, and personal records.

Terminal window
curl "https://api.yourdomain.com/v1/users/01JA4MNPQR8STUVWXYZ00001/connections" \
-H "Authorization: Bearer $VITASYNC_API_KEY"

Response:

[
{
"id": "01JA4MNPQR8STUVWXYZ00002",
"userId": "01JA4MNPQR8STUVWXYZ00001",
"providerId": "fitbit",
"status": "active",
"lastSyncedAt": "2025-06-15T08:30:00.000Z",
"connectedAt": "2025-06-01T10:05:00.000Z"
}
]

Connection statuses:

StatusMeaning
activeTokens valid, syncing normally
errorLast sync failed; check webhook sync.failed events for details
disconnectedUser revoked access at the provider

Removes the connection and deletes stored tokens. Requires write scope. Does not delete health data already synced — use DELETE /v1/users/:userId/health for GDPR erasure.

Terminal window
curl -X DELETE \
"https://api.yourdomain.com/v1/users/01JA4MNPQR8STUVWXYZ00001/connections/01JA4MNPQR8STUVWXYZ00002" \
-H "Authorization: Bearer $VITASYNC_API_KEY"
# 204 No Content
Provider IDNameAuth Protocol
fitbitFitbitOAuth 2.0 + PKCE
garminGarminOAuth 1.0a (HMAC-SHA1)
whoopWHOOPOAuth 2.0
stravaStravaOAuth 2.0

See the Providers section for per-provider setup instructions, OAuth app creation steps, and full metric coverage tables.