Skip to Content
DeploymentEnvironment Variables

Environment Variables

Complete reference for all environment variables used across the Loop Health platform.

Database

VariableRequiredDescription
DATABASE_URLYesPostgreSQL connection string
SUPABASE_URLYesSupabase project URL
SUPABASE_ANON_KEYYesSupabase anonymous (public) key
SUPABASE_SERVICE_KEYYesSupabase service role key (server-side only)

Authentication (Clerk)

VariableRequiredDescription
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEYYesClerk publishable key (client-side)
CLERK_SECRET_KEYYesClerk secret key (server-side)
CLERK_ISSUER_URLYesClerk issuer URL for JWT verification

AI Providers

VariableRequiredDescription
OPENAI_API_KEYLuna, EmbeddingsOpenAI API key
ANTHROPIC_API_KEYLuna, Biomarker ParserAnthropic API key

SSO

VariableRequiredDescription
SSO_PRIVATE_KEYAdminSSO private key for token signing
SSO_PUBLIC_KEYConsumerSSO public key for token verification
SSO_JWT_SECRETBothShared secret for HS256 SSO tokens
JWT_SECRETAPIGeneral JWT secret

Wearables

VariableRequiredDescription
OURA_CLIENT_IDConsumerOura OAuth client ID
OURA_CLIENT_SECRETConsumerOura OAuth client secret
WHOOP_CLIENT_IDConsumerWhoop OAuth client ID
WHOOP_CLIENT_SECRETConsumerWhoop OAuth client secret
WHOOP_REDIRECT_URIConsumerWhoop OAuth redirect URI
DEXCOM_CLIENT_IDConsumerDexcom OAuth client ID
DEXCOM_CLIENT_SECRETConsumerDexcom OAuth client secret
LIBRE_CLIENT_IDConsumerLibre OAuth client ID
LIBRE_CLIENT_SECRETConsumerLibre OAuth client secret

Background Jobs (Trigger.dev)

VariableRequiredDescription
TRIGGER_API_KEYTriggerTrigger.dev API key
TRIGGER_API_URLTriggerTrigger.dev API URL
TRIGGER_PROJECT_IDTriggerTrigger.dev project ID

Activity Feeds (GetStream)

VariableRequiredDescription
STREAM_API_KEYConsumerGetStream API key
STREAM_API_SECRETConsumerGetStream API secret

Notifications (Knock)

VariableRequiredDescription
KNOCK_SECRET_KEYConsumerKnock secret key
KNOCK_API_KEYConsumerKnock API key
KNOCK_SIGNING_KEYConsumerKnock webhook signing key

Commerce (BigCommerce)

VariableRequiredDescription
BIGCOMMERCE_STORE_HASHConsumerBigCommerce store hash
BIGCOMMERCE_ACCESS_TOKENConsumerBigCommerce API access token
BIGCOMMERCE_API_URLConsumerBigCommerce API URL

Commerce (Bolt)

VariableRequiredDescription
BOLT_API_KEYConsumerBolt checkout API key
BOLT_PUBLISHABLE_KEYConsumerBolt publishable key

Rimo Health

VariableRequiredDescription
RIMO_WEBHOOK_SECRETPatient GraphRimo webhook HMAC secret
RIMO_API_URLConsumerRimo Health API URL
RIMO_API_KEYConsumerRimo Health API key

Voice (Vapi)

VariableRequiredDescription
VAPI_API_KEYLunaVapi API key for voice calls
VAPI_WEBHOOK_SECRETLunaVapi webhook secret

Monitoring

VariableRequiredDescription
SENTRY_DSNAllSentry DSN for error tracking
SENTRY_AUTH_TOKENCISentry auth token for source maps
NEXT_PUBLIC_POSTHOG_KEYConsumerPostHog project key
NEXT_PUBLIC_POSTHOG_HOSTConsumerPostHog host URL

Caching (Upstash Redis)

VariableRequiredDescription
UPSTASH_REDIS_REST_URLCoreUpstash Redis REST URL
UPSTASH_REDIS_REST_TOKENCoreUpstash Redis REST token

Cron Jobs

VariableRequiredDescription
CRON_SECRETConsumerSecret for authenticating cron requests

Platform API

VariableRequiredDescription
PLATFORM_API_KEYAdmin, ConsumerAPI key for platform internal API

Patient Graph API

VariableRequiredDescription
PATIENT_GRAPH_API_URLConsumer, LunaPatient Graph API base URL
PATIENT_GRAPH_API_KEYConsumer, LunaPatient Graph API authentication key

Junction Health (Wearables)

VariableRequiredDescription
JUNCTION_API_KEYPatient GraphJunction Health API key for unified wearables
JUNCTION_API_URLPatient GraphJunction Health API base URL

Environment-Specific Values

Development

# .env.local DATABASE_URL="postgresql://postgres:postgres@localhost:5432/loop_dev" SUPABASE_URL="https://your-project.supabase.co" SUPABASE_ANON_KEY="eyJ..." NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..." CLERK_SECRET_KEY="sk_test_..." OPENAI_API_KEY="sk-..." UPSTASH_REDIS_REST_URL="https://localhost:8079" UPSTASH_REDIS_REST_TOKEN="local"

Staging

# Vercel staging environment DATABASE_URL="postgresql://user:pass@staging.supabase.co:5432/loop_staging?pool=1&pgbouncer=true" SUPABASE_URL="https://staging-project.supabase.co" SUPABASE_ANON_KEY="eyJ..." NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_..." CLERK_SECRET_KEY="sk_test_..." CLERK_ISSUER_URL="https://staging.clerk.loop.health" SENTRY_DSN="https://...@sentry.io/staging" SENTRY_ENVIRONMENT="staging"

Production

# Vercel production environment DATABASE_URL="postgresql://user:pass@prod.supabase.co:5432/loop_production?pool=1&pgbouncer=true" SUPABASE_URL="https://prod-project.supabase.co" SUPABASE_ANON_KEY="eyJ..." SUPABASE_SERVICE_KEY="eyJ..." NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_..." CLERK_SECRET_KEY="sk_live_..." CLERK_ISSUER_URL="https://clerk.loop.health" SENTRY_DSN="https://...@sentry.io/production" SENTRY_ENVIRONMENT="production" SENTRY_RELEASE="${VERCEL_GIT_COMMIT_SHA}"

Security Best Practices

Secret Rotation Schedule

Secret TypeRotation FrequencyOwnerPriority
Database passwords90 daysDevOpsHigh
API keys (external services)180 daysEngineeringMedium
JWT signing keys365 daysSecurityCritical
Webhook secretsOn breach onlyEngineeringHigh
OAuth client secrets180 daysEngineeringMedium

Rotation Procedure

# 1. Generate new secret NEW_SECRET=$(openssl rand -base64 32) # 2. Set in Vercel (keep old value temporarily) vercel env add DATABASE_PASSWORD production # Paste new value # 3. Deploy with new secret vercel --prod # 4. Verify deployment healthy curl https://my.loop.health/api/health # 5. Remove old secret value # (Old value kept for rollback during grace period)

Secret Storage

DO ✅:

  • Use Vercel environment variables for runtime secrets
  • Use GitHub Secrets for CI/CD secrets
  • Rotate secrets on schedule
  • Use different secrets per environment
  • Audit secret access logs
  • Use service accounts (not personal API keys)
  • Enable MFA on secret management accounts

DON’T ❌:

  • Commit secrets to Git (even in .env files)
  • Share secrets via Slack/email
  • Use same secret across environments
  • Use personal API keys in production
  • Hard-code secrets in source code
  • Log secrets (even in debug mode)
  • Store secrets in unencrypted files

Secret Validation

// Validate required secrets at startup const requiredSecrets = [ 'DATABASE_URL', 'CLERK_SECRET_KEY', 'SENTRY_DSN', ]; for (const secret of requiredSecrets) { if (!process.env[secret]) { throw new Error(`Missing required environment variable: ${secret}`); } } // Validate secret format if (!process.env.DATABASE_URL?.startsWith('postgresql://')) { throw new Error('DATABASE_URL must be a valid PostgreSQL connection string'); }

Connection String Patterns

PostgreSQL (Supabase)

Format:

postgresql://[user]:[password]@[host]:[port]/[database]?[params]

Required parameters:

  • pool=1 - Single connection per instance (Vercel Edge Functions)
  • pgbouncer=true - Use Supabase connection pooler

Example:

DATABASE_URL="postgresql://postgres.abc123:password@aws-0-us-east-1.pooler.supabase.com:5432/postgres?pool=1&pgbouncer=true"

Why these params?

  • Vercel Edge Functions are serverless and ephemeral
  • Each function invocation creates a new connection
  • Without pooling, you hit connection limits quickly
  • Supabase pgbouncer pools connections efficiently

Redis (Upstash)

Format:

https://[id].upstash.io

Example:

UPSTASH_REDIS_REST_URL="https://abc-123-xyz.upstash.io" UPSTASH_REDIS_REST_TOKEN="AX..."

Clerk (Authentication)

Issuer URL format:

https://[subdomain].clerk.accounts.dev # Development https://clerk.[yourdomain].com # Production

Example:

CLERK_ISSUER_URL="https://clerk.loop.health" NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_live_..." CLERK_SECRET_KEY="sk_live_..."

Troubleshooting

Missing Environment Variables

Symptom: Runtime error process.env.X is undefined

Fix:

# 1. Check if variable is set in Vercel vercel env ls # 2. Add missing variable vercel env add MISSING_VAR production # 3. Redeploy vercel --prod # 4. Verify in deployment logs vercel logs [deployment-url]

Database Connection Errors

Symptom: Connection pool exhausted or ECONNREFUSED

Common causes:

  1. Missing pool=1&pgbouncer=true parameters
  2. Wrong database host/port
  3. Firewall blocking connections
  4. Database credentials expired

Fix:

# Verify connection string format echo $DATABASE_URL | grep "pool=1" | grep "pgbouncer=true" # Test connection manually psql "$DATABASE_URL" -c "SELECT 1;" # Check Supabase pooler status # Dashboard → Database → Connection Pooling

Clerk Authentication Failures

Symptom: Invalid JWT signature or Issuer mismatch

Common causes:

  1. Wrong CLERK_ISSUER_URL (must match Clerk dashboard)
  2. Mismatched publishable key and secret key
  3. Clock skew (JWT exp/iat validation)

Fix:

# Verify issuer URL matches Clerk dashboard # Dashboard → API Keys → Issuer # Ensure keys match environment # pk_test_* → sk_test_* # pk_live_* → sk_live_* # Check system time date -u # Should be within 5 minutes of actual UTC

API Key Validation Errors

Symptom: 401 Unauthorized from external API

Common causes:

  1. API key expired
  2. Wrong API key for environment (test vs prod)
  3. API key format incorrect (missing prefix)
  4. Rate limit exceeded

Fix:

# Test API key manually curl -H "Authorization: Bearer $OPENAI_API_KEY" \ https://api.openai.com/v1/models # Check key format # OpenAI: sk-... # Anthropic: sk-ant-... # Rimo: rm_... # Rotate key if expired # (See Secret Rotation above)

Per-Service Configuration

my.loop.health (Consumer App)

Platform: Vercel Environment Variables: 25+

Critical variables:

  • DATABASE_URL - Supabase PostgreSQL
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - Clerk auth (public)
  • CLERK_SECRET_KEY - Clerk auth (secret)
  • PATIENT_GRAPH_API_URL - Patient Graph API
  • PATIENT_GRAPH_API_KEY - Patient Graph auth
  • SENTRY_DSN - Error tracking
  • UPSTASH_REDIS_REST_URL - Caching
  • UPSTASH_REDIS_REST_TOKEN - Caching auth

Setup:

cd apps/my-loop-health vercel link vercel env pull .env.local

apps/patient-graph (Patient Graph API)

Platform: Vercel (or Render/Fly.io - see deployment docs) Environment Variables: 15+

Critical variables:

  • DATABASE_URL - Supabase PostgreSQL (health project)
  • CLERK_ISSUER_URL - JWT validation
  • CLERK_SECRET_KEY - JWT validation
  • RIMO_WEBHOOK_SECRET - Rimo webhook validation
  • JUNCTION_API_KEY - Wearables API
  • SENTRY_DSN - Error tracking
  • UPSTASH_REDIS_REST_URL - Caching
  • UPSTASH_REDIS_REST_TOKEN - Caching auth

Setup:

cd apps/patient-graph vercel link vercel env pull .env.local

apps/admin (Admin Dashboard)

Platform: Vercel Environment Variables: 20+

Critical variables:

  • DATABASE_URL - Supabase PostgreSQL (both projects)
  • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY - Clerk auth
  • CLERK_SECRET_KEY - Clerk auth
  • SSO_PRIVATE_KEY - SSO token signing
  • PLATFORM_API_KEY - Internal API auth
  • SENTRY_DSN - Error tracking

Setup:

cd apps/admin vercel link vercel env pull .env.local

Verification Checklist

Before deploying to production, verify:

  • All required variables set in Vercel environment
  • Database connection string includes pool=1&pgbouncer=true
  • Clerk keys match environment (test vs live)
  • Sentry DSN points to correct project
  • API keys are production (not test) keys
  • Webhook secrets match external service configuration
  • Redis credentials are production (not local)
  • All secrets different from staging
  • No secrets committed to Git
  • Secrets rotation schedule documented
  • Test deployment with vercel --prod (from branch)
  • Verify health check passes: /api/health
  • Check Sentry for deployment errors

Additional Resources