Skip to content

Data Model

import { Aside } from ‘@astrojs/starlight/components’;

VitaSync stores all health data in PostgreSQL using Drizzle ORM. This reference explains every table, the relationships between them, and the JSON shapes used for complex metrics.

workspaces
|
+-- users (many per workspace)
|
+-- provider_connections (one per provider per user)
| |
| +-- sync_jobs
| +-- health_metrics (via userId + providerId)
| +-- events (via userId + providerId)
|
+-- personal_records (one per metricType+category per user)
|
+-- notification_channels (many per user)
| |
| +-- notification_logs (delivery audit trail)
|
+-- notification_rules (route categories → channels)
|
+-- mood_logs (mood & mental wellness)
+-- journal_entries (daily journal)
+-- water_intake (hydration tracking)
+-- habits (habit definitions)
| +-- habit_logs (daily completions)
+-- nutrition_logs (meal/food tracking)
+-- medications (medication tracking)
+-- symptom_logs (symptom occurrences)
+-- goals (health goals)
+-- achievements (unlocked badges)
+-- training_plans (workout plans)
+-- health_scores (composite wellness scores)
+-- health_reports (generated reports)
+-- health_snapshots (point-in-time snapshots)
+-- biometric_baselines (rolling baselines)
+-- anomaly_alerts (detected anomalies)
+-- correlations (metric relationships)
+-- training_load (fitness/fatigue/form)
workspaces
|
+-- api_keys
+-- webhooks
|
+-- webhook_deliveries

Top-level tenant. All data is isolated per workspace.

ColumnTypeNotes
idULIDPrimary key
nametextDisplay name
slugtextURL-safe unique identifier
createdAttimestamptz
updatedAttimestamptz

A user corresponds to one person with one or more wearable devices.

ColumnTypeNotes
idULIDPrimary key
workspaceIdULIDFK → workspaces.id
externalIdtextYour system’s user ID. Unique per workspace
emailtextOptional
displayNametextOptional
metadatajsonbArbitrary key-value data you want to store alongside the user
createdAttimestamptz
updatedAttimestamptz

Unique constraint: (workspaceId, externalId)

Workspace-level API keys used to authenticate all API requests.

ColumnTypeNotes
idULIDPrimary key
workspaceIdULIDFK → workspaces.id
nametextHuman-readable label
keyHashtextSHA-256 hash of the raw key — raw key never stored
keyPrefixtextFirst 8 characters of the raw key (for identification in logs)
scopestext[]Array of: read, write, admin
expiresAttimestamptzNull = never expires
lastUsedAttimestamptzUpdated on every authenticated request
createdAttimestamptz

Stores OAuth tokens for each user-provider pair. Tokens are encrypted before storage.

ColumnTypeNotes
idULIDPrimary key
userIdULIDFK → users.id
providerIdtextfitbit, garmin, whoop, strava
encryptedTokenstextAES-256-GCM encrypted JSON (accessToken, refreshToken, expiresAt)
statustextactive, error, disconnected
lastSyncedAttimestamptzNull until first sync completes
connectedAttimestamptzWhen OAuth was completed
createdAttimestamptz
updatedAttimestamptz

Unique constraint: (userId, providerId) — one connection per provider per user.

Stores individual health data points from provider syncs.

ColumnTypeNotes
idULIDPrimary key
userIdULIDFK → users.id
providerIdtextWhich provider supplied this data point
metricTypetextSee Metric Types below
valuenumericScalar value for simple metrics (null for complex metrics)
unittextUnit string, e.g. count, bpm, kg, meters
datajsonbStructured data for complex metrics (sleep stages, etc.) — null for scalar metrics
sourcetextuser (device-measured), manual, or computed
recordedAttimestamptzWhen the device recorded the measurement
createdAttimestamptzWhen VitaSync ingested it

Unique constraint: (userId, providerId, metricType, recordedAt) — prevents duplicate ingestion across multiple syncs.

Structured events such as workout sessions and sleep sessions. These are richer than scalar metrics and stored separately.

ColumnTypeNotes
idULIDPrimary key
userIdULIDFK → users.id
providerIdtext
providerEventIdtextProvider-assigned event ID (for deduplication)
eventTypetextworkout, sleep, activity
activityTypetextFor workouts: running, cycling, swimming, strength_training, etc.
startedAttimestamptzEvent start time
endedAttimestamptzEvent end time
durationSecondsintegerDuration in seconds
distanceMetersnumericDistance for movement-based activities
caloriesKcalnumericCalories burned
avgHeartRateintegerAverage heart rate (bpm)
maxHeartRateintegerMaximum heart rate (bpm)
datajsonbProvider-specific extended data (laps, elevation, sleep stages, etc.)
createdAttimestamptz

Unique constraint: (userId, providerId, providerEventId).

Tracks lifetime bests per metric type for each user. Automatically updated after every successful sync.

ColumnTypeNotes
idULIDPrimary key
userIdULIDFK → users.id
metricTypetextSee Metric Types below
categorytexte.g. daily_max, daily_min, all_time_max
valuenumericThe record value
unittext
recordedAttimestamptzWhen the record was set
providerIdtextWhich provider’s data set this record
updatedAttimestamptz

Unique constraint: (userId, metricType, category).

Registered HTTP endpoints for event delivery.

ColumnTypeNotes
idULIDPrimary key
workspaceIdULIDFK → workspaces.id
urltextYour HTTPS endpoint
secretHashtextHMAC signing secret, hashed for storage
eventstext[]Subscribed event types
isActivebooleanIf false, no deliveries are made
descriptiontextOptional label
createdAttimestamptz

Delivery log for every attempted webhook event delivery.

ColumnTypeNotes
idULIDPrimary key
webhookIdULIDFK → webhooks.id
eventtextEvent type string
payloadjsonbFull request body that was sent
statustextpending, delivered, failed
statusCodeintegerHTTP response code from your server
attemptCountintegerNumber of delivery attempts so far
deliveredAttimestamptzNull until successfully delivered
createdAttimestamptz

Tracks the state of each background sync execution.

ColumnTypeNotes
idULIDPrimary key
connectionIdULIDFK → provider_connections.id
statustextpending, running, completed, failed
metricsSyncedintegerNumber of data points written
errortextError message if status is failed
startedAttimestamptz
completedAttimestamptz
createdAttimestamptz

Stores user-configured notification channel instances. Each row represents a specific channel (e.g. “Work Slack”, “Personal Discord”).

ColumnTypeNotes
idUUIDPrimary key
userIdUUIDFK → users.id (cascade delete)
channelTypevarchar(30)discord, slack, teams, email, push, ntfy, webhook
labelvarchar(100)Human-readable name
configjsonbChannel-specific settings (webhook URL, SMTP config, etc.)
enabledbooleanWhether this channel is active
createdAttimestamptz
updatedAttimestamptz

Indexed on (userId) and (userId, channelType).

Determines which events trigger which channels. A rule links event categories and minimum severity to a set of notification channels.

ColumnTypeNotes
idUUIDPrimary key
userIdUUIDFK → users.id (cascade delete)
namevarchar(100)Human-readable rule name
categoriesjsonbArray of categories: anomaly, goal, achievement, sync, report, system, insight
minSeverityvarchar(20)Minimum severity to match: info, warning, critical
channelIdsjsonbArray of notification_channels.id to deliver to
enabledbooleanWhether the rule is active
createdAttimestamptz
updatedAttimestamptz

Indexed on (userId).

Delivery audit log for every notification dispatch attempt.

ColumnTypeNotes
idUUIDPrimary key
userIdUUIDFK → users.id (cascade delete)
channelIdUUIDFK → notification_channels.id (cascade delete)
channelTypevarchar(30)Denormalized for fast querying
titlevarchar(255)Notification title
payloadjsonbFull payload sent
statusvarchar(20)pending, delivered, failed
attemptsintegerNumber of delivery attempts
errorvarchar(2000)Error message if failed
deliveredAttimestamptzNull until successful
createdAttimestamptz

Indexed on (userId), (channelId), and (status).


TypeUnitDescription
stepscountStep count
distancemetersDistance traveled
calorieskcalActive calories burned
active_minutesminutesTime in moderate+ intensity zones
floorscountFloors climbed (Fitbit)
TypeUnitDescription
heart_ratebpmInstantaneous or average heart rate
resting_heart_ratebpmDaily resting heart rate
heart_rate_variabilitymsRMSSD-based HRV (overnight)
TypeUnitDescription
sleephoursSleep duration; stage breakdown in data
sleep_scorescoreComposite sleep quality score (0–100)
TypeUnitDescription
weightkgBody weight
body_fatpercentBody fat percentage
bmiindexBody mass index
blood_oxygenpercentBlood oxygen via pulse oximetry
blood_pressuremmHgSystolic/diastolic in data.systolic/data.diastolic
temperaturecelsiusSkin or core temperature
blood_glucosemmol_lBlood glucose
TypeUnitDescription
stressscoreStress score (Garmin: 0–100)
hrv_statusstatusHRV band: poor, balanced, good, optimal
recovery_scorepercentWHOOP recovery (0–100%)
readiness_scorescoreDaily readiness (0–100)
strain_scorescoreWHOOP daily strain (0–21)
TypeUnitDescription
respiratory_ratebreaths_per_minBreaths per minute (overnight average)
spo2percentSpO2 from pulse oximetry
TypeDescription
workoutWorkout session marker; full data in the Events API

{
"stages": {
"deep": 95,
"light": 220,
"rem": 110,
"awake": 20
},
"efficiency": 88,
"consistency": 74,
"startTime": "2025-06-05T23:15:00.000Z",
"endTime": "2025-06-06T07:00:00.000Z",
"score": 82
}
{
"systolic": 118,
"diastolic": 76,
"pulse": 68
}
{
"min": 46,
"max": 148,
"resting": 56,
"zones": {
"fat_burn": 42,
"cardio": 28,
"peak": 8
}
}
{
"sport": "running",
"elevationGain": 85,
"elevationLoss": 82,
"avgPace": "6:15",
"avgCadence": 168,
"avgPower": null,
"laps": [
{ "lapNumber": 1, "distance": 1000, "time": 375, "avgHR": 152 }
],
"hrZones": {
"zone1": 180,
"zone2": 420,
"zone3": 810,
"zone4": 1080,
"zone5": 210
}
}

Daily journal entries with mood tagging, gratitude lists, and searchable content.

ColumnTypeNotes
iduuidPK, auto-generated
user_iduuidFK → users, cascade delete
titlevarchar(200)Optional title/headline
bodytextMain journal content (markdown-friendly)
mood_scoredoubleMood 1–5 at time of writing
mood_labelvarchar(50)happy, calm, anxious, sad, energized, tired, grateful, reflective
gratitudejsonbArray of gratitude strings
tagsjsonbArray of tag strings
entry_datetimestamptzWhen the entry is for
created_attimestamptzRow creation time
updated_attimestamptzLast update time

Index: (user_id, entry_date)

Individual hydration logs with beverage type and daily goal tracking.

ColumnTypeNotes
iduuidPK, auto-generated
user_iduuidFK → users, cascade delete
amount_mlintegerAmount in milliliters
beverage_typevarchar(30)water, tea, coffee, juice, other
notevarchar(200)Optional context note
daily_goal_mlintegerDaily goal snapshot (default 2500)
logged_attimestamptzWhen the intake was logged
created_attimestamptzRow creation time

Index: (user_id, logged_at)

User-defined habit definitions with streak tracking.

ColumnTypeNotes
iduuidPK, auto-generated
user_iduuidFK → users, cascade delete
namevarchar(100)Habit name
iconvarchar(10)Emoji icon
colorvarchar(20)UI color (blue, green, red, etc.)
frequencyvarchar(20)daily, weekdays, custom
target_daysjsonbArray of day numbers (0=Sun, 6=Sat)
activebooleanWhether the habit is active
current_streakintegerCurrent consecutive day streak
longest_streakintegerBest streak ever
created_attimestamptzRow creation time
updated_attimestamptzLast update time

Index: (user_id)

One row per habit per day when completed.

ColumnTypeNotes
iduuidPK, auto-generated
habit_iduuidFK → habits, cascade delete
user_iduuidFK → users, cascade delete
completed_datedateThe date the habit was completed
notevarchar(200)Optional note
created_attimestamptzRow creation time

Indexes: (habit_id, completed_date), (user_id, completed_date)