> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Environment Variables Guide

> Version: 1.2.0 Last Updated: 2026-03-12 Target Audience: Developers

**Version:** 1.2.0\
**Last Updated:** 2026-03-12\
**Target Audience:** Developers

This guide documents all environment variables used in the Encore Health OS Platform, including required variables, optional variables, and security best practices.

***

## Quick Start

1. Copy `.env.example` to `.env.local`
2. Fill in your Supabase credentials
3. Never commit `.env.local` to version control

***

## Required Variables

### Supabase Configuration

**`VITE_SUPABASE_URL`** (Required)

* **Description:** Your Supabase project URL
* **Format:** `https://{project-ref}.supabase.co`
* **Example:** `https://abcdefghijklmnop.supabase.co`
* **Where to get:** Supabase Dashboard → Settings → API → Project URL
* **Client Access:** ✅ Exposed to client (VITE\_ prefix)
* **Security:** Safe to expose (public URL)

**`VITE_SUPABASE_PUBLISHABLE_KEY`** (Required)

* **Description:** Supabase anon/public key for client-side access
* **Format:** `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
* **Where to get:** Supabase Dashboard → Settings → API → anon public key
* **Client Access:** ✅ Exposed to client (VITE\_ prefix)
* **Security:** Safe to expose (public key, RLS enforced)
* **Note:** This is the "anon" key, not the service role key

***

## Development/Testing Variables

### Service Role Key (Testing Only)

**`SUPABASE_SERVICE_ROLE_KEY`** (Development Only)

* **Description:** Supabase service role key - bypasses RLS
* **Format:** `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...`
* **Where to get:** Supabase Dashboard → Settings → API → service\_role key
* **Client Access:** ❌ NEVER use in client code
* **Security:** ⚠️ **CRITICAL** - Bypasses all RLS policies
* **Usage:** Only in test files and edge functions
* **Warning:** Never commit this value, never use in production client code

***

## Optional Variables (Future)

### Feature Flags

**`VITE_ENABLE_ANALYTICS`** (Optional)

* **Description:** Enable analytics tracking
* **Type:** Boolean
* **Default:** `false`
* **Values:** `true` | `false`
* **Client Access:** ✅ Exposed to client

**`VITE_ENABLE_ERROR_TRACKING`** (Optional)

* **Description:** Enable error tracking (Sentry, LogRocket)
* **Type:** Boolean
* **Default:** `false`
* **Values:** `true` | `false`
* **Client Access:** ✅ Exposed to client

**`VITE_ENABLE_AI_FEATURES`** (Optional)

* **Description:** Enable AI-powered features
* **Type:** Boolean
* **Default:** `true`
* **Values:** `true` | `false`
* **Client Access:** ✅ Exposed to client

***

## Edge Function Secrets

Edge function secrets are **NOT** set in `.env.local`. They are set via Supabase CLI:

```bash theme={null}
supabase secrets set TWILIO_ACCOUNT_SID=ACxxxxx
supabase secrets set TWILIO_AUTH_TOKEN=xxxxx
supabase secrets set TWILIO_PHONE_NUMBER=+1234567890
supabase secrets set OPENROUTER_API_KEY=sk-or-v1-xxxxx
```

### Scheduled Tasks / CRON

**`CRON_SECRET`** (Required for cron-triggered edge functions)

* **Functions:** `gr-coi-attestation-reminders`, `hr-credential-expiry-check`, and other scheduled functions
* **Description:** Shared secret used to authenticate cron-triggered edge functions. The function validates `Authorization: Bearer <CRON_SECRET>` and rejects requests where the header is missing or mismatched.
* **Format:** Random string (min 32 characters recommended)
* **Set via:** `supabase secrets set CRON_SECRET=<random-string>`
* **Security:** ⚠️ **CRITICAL** — Must be fail-closed: if `CRON_SECRET` is not set, the function must throw (not skip auth)

### Encryption Keys

**`BANK_ENCRYPTION_KEY`** (Required for financial data encryption)

* **Functions:** `fa-bank-account-encrypt`, account verification flows
* **Description:** AES-256-GCM key for encrypting sensitive bank account data (routing numbers, account numbers) at rest
* **Format:** Base64-encoded 256-bit key
* **Set via:** `supabase secrets set BANK_ENCRYPTION_KEY=<base64-key>`
* **Security:** ⚠️ **CRITICAL** — Loss of this key means encrypted bank data is irrecoverable. Rotate via re-encryption migration.

**`SSN_ENCRYPTION_KEY`** (Required for SSN/TIN encryption)

* **Functions:** `hr-ssn-encrypt`, payroll processing, tax form generation
* **Description:** AES-256-GCM key for encrypting Social Security Numbers and Tax Identification Numbers
* **Format:** Base64-encoded 256-bit key
* **Set via:** `supabase secrets set SSN_ENCRYPTION_KEY=<base64-key>`
* **Security:** ⚠️ **CRITICAL** — PHI/PII protection. Must never appear in logs or error messages.

### Portal Authentication

**`PORTAL_JWT_SECRET`** (Required for external portal access)

* **Functions:** `portal-auth`, `portal-verify-token`
* **Description:** HMAC-SHA256 secret for signing and verifying JWTs issued to external portal users (e.g., partner portals, resident portals)
* **Format:** Random string (min 64 characters recommended)
* **Set via:** `supabase secrets set PORTAL_JWT_SECRET=<random-string>`
* **Security:** ⚠️ Separate from Supabase Auth JWT secret; used only for portal-scoped tokens

**`PORTAL_BASE_URL`** (Required for portal links)

* **Functions:** `portal-invite`, notification emails containing portal links
* **Description:** Base URL for the external-facing portal (used to construct invite/reset links)
* **Format:** `https://portal.your-domain.com`
* **Set via:** `supabase secrets set PORTAL_BASE_URL=https://portal.your-domain.com`

### Email Service (Entra ID & Gmail)

Org-level email uses Microsoft Entra ID (Graph API) or Gmail (Google Workspace).

**Entra ID (optional):**

* **`ENTRA_CLIENT_SECRET`** — Client secret for the Entra app registration used for Graph API (e.g. Mail.Send).
* **Set via:** `supabase secrets set ENTRA_CLIENT_SECRET=xxxxx`
* **Per-org:** Configure in Settings → Integrations → Microsoft Entra ID (tenant, app, sender email).

**Gmail (optional):**

* **`GMAIL_SERVICE_ACCOUNT_JSON`** — Full JSON key for the Google Cloud service account (with domain-wide delegation for Gmail API).
* **Set via:** `supabase secrets set GMAIL_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}'`
* **Per-org:** Set sender email in Settings → Integrations → Google Workspace (Gmail).

### SMS Service (Twilio)

**`TWILIO_ACCOUNT_SID`**

* **Function:** `send-sms-notification`
* **Where to get:** [Twilio Console](https://console.twilio.com/)
* **Format:** `ACxxxxx`
* **Set via:** `supabase secrets set TWILIO_ACCOUNT_SID=ACxxxxx`

**`TWILIO_AUTH_TOKEN`**

* **Function:** `send-sms-notification`
* **Where to get:** [Twilio Console](https://console.twilio.com/)
* **Format:** `xxxxx`
* **Set via:** `supabase secrets set TWILIO_AUTH_TOKEN=xxxxx`

**`TWILIO_PHONE_NUMBER`**

* **Function:** `send-sms-notification`
* **Where to get:** [Twilio Console](https://console.twilio.com/) → Phone Numbers
* **Format:** `+1234567890`
* **Set via:** `supabase secrets set TWILIO_PHONE_NUMBER=+1234567890`

### AI Service (OpenRouter) - PF-59

**`OPENROUTER_API_KEY`** (Required for AI features)

* **Functions:** `ai-assistant`, `ai-document-analyze`, `generate-report-narrative`
* **Where to get:** [OpenRouter Dashboard](https://openrouter.ai/keys)
* **Format:** `sk-or-v1-xxxxx`
* **Set via:** `supabase secrets set OPENROUTER_API_KEY=sk-or-v1-xxxxx`
* **Note:** Replaced LOVABLE\_API\_KEY as of PF-59 migration

**`APP_URL`** (Required for AI features)

* **Functions:** All AI-related edge functions
* **Description:** Application URL for OpenRouter attribution (HTTP-Referer header)
* **Format:** `https://your-app.lovable.app`
* **Set via:** `supabase secrets set APP_URL=https://data-mover-pal.lovable.app`

#### Model Selection by Module

OpenRouter routes to different models based on module context:

| Module            | Model                         | Reason                           |
| ----------------- | ----------------------------- | -------------------------------- |
| `gr` (Governance) | `anthropic/claude-3.5-sonnet` | Complex reasoning for compliance |
| `hr` (HR)         | `anthropic/claude-3.5-sonnet` | Nuanced policy interpretation    |
| `fa` (Finance)    | `openai/gpt-4o`               | Financial analysis accuracy      |
| `fw` (Workflows)  | `openai/gpt-4o`               | Workflow optimization            |
| `lo` (Leadership) | `openai/gpt-4o-mini`          | Fast meeting summaries           |
| `rh` (Housing)    | `openai/gpt-4o`               | Resident data handling           |
| `fm` (Facilities) | `openai/gpt-4o-mini`          | Quick work order processing      |
| `pf` (Platform)   | `openai/gpt-4o`               | Default for platform features    |

***

## Environment-Specific Configuration

### Development

**File:** `.env.local` (gitignored)

```bash theme={null}
VITE_SUPABASE_URL=https://dev-project.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=dev-anon-key
SUPABASE_SERVICE_ROLE_KEY=dev-service-key  # Testing only
```

### Staging

**Set via:** Vercel Environment Variables or Supabase Dashboard

```bash theme={null}
VITE_SUPABASE_URL=https://staging-project.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=staging-anon-key
# No service role key in staging client
```

### Production

**Set via:** Vercel Environment Variables or Supabase Dashboard

```bash theme={null}
VITE_SUPABASE_URL=https://prod-project.supabase.co
VITE_SUPABASE_PUBLISHABLE_KEY=prod-anon-key
# No service role key in production client
```

***

## Security Best Practices

### 1. Never Commit Secrets

**✅ DO:**

* Use `.env.local` for local development
* Add `.env.local` to `.gitignore`
* Use `.env.example` as template (no real values)

**❌ DON'T:**

* Commit `.env.local` with real values
* Put secrets in `.env.example`
* Hardcode secrets in code
* Share secrets in chat/email

### 2. VITE\_ Prefix Rules

**All `VITE_*` variables are exposed to the client bundle:**

* ✅ Safe: Public URLs, anon keys, feature flags
* ❌ Never: Service role keys, API secrets, passwords

**Example:**

```bash theme={null}
# ✅ SAFE - Public URL
VITE_SUPABASE_URL=https://project.supabase.co

# ❌ DANGEROUS - Secret exposed to client
VITE_SUPABASE_SERVICE_ROLE_KEY=xxxxx  # WRONG!
```

### 3. Edge Function Secrets

**Edge function secrets are separate:**

* Set via `supabase secrets set KEY=value`
* Never in `.env.local` or client code
* Only accessible in edge function runtime

### 4. Service Role Key

**Service role key is CRITICAL:**

* ⚠️ Bypasses all RLS policies
* ❌ Never use in client code
* ✅ Only in test files and edge functions
* ✅ Only in development/staging

***

## Variable Validation

### Required Variables Check

The application validates required variables on startup:

```typescript theme={null}
// src/integrations/supabase/client.ts
const SUPABASE_URL = import.meta.env.VITE_SUPABASE_URL;
const SUPABASE_PUBLISHABLE_KEY = import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY;

if (!SUPABASE_URL || !SUPABASE_PUBLISHABLE_KEY) {
  throw new Error('Missing required Supabase environment variables');
}
```

### Development Warnings

The app will warn if:

* Required variables are missing
* Service role key is used in client code (development only)
* Invalid Supabase URL format

***

## How to Obtain Keys

### Supabase Keys

1. Go to [Supabase Dashboard](https://app.supabase.com)
2. Select your project
3. Navigate to **Settings** → **API**
4. Copy:
   * **Project URL** → `VITE_SUPABASE_URL`
   * **anon public key** → `VITE_SUPABASE_PUBLISHABLE_KEY`
   * **service\_role key** → `SUPABASE_SERVICE_ROLE_KEY` (testing only)

### Email Provider Secrets

All email (notifications, HR, automation, and signature flows) is sent via the shared org-level email-provider (Entra ID or Gmail).

* Entra: `ENTRA_CLIENT_SECRET`
* Gmail: `GMAIL_SERVICE_ACCOUNT_JSON`

### Twilio Credentials

1. Go to [Twilio Console](https://console.twilio.com)
2. Navigate to **Account** → **API Keys & Tokens**
3. Copy:
   * **Account SID** → `TWILIO_ACCOUNT_SID`
   * **Auth Token** → `TWILIO_AUTH_TOKEN`
4. Navigate to **Phone Numbers** → **Manage** → **Active Numbers**
5. Copy phone number → `TWILIO_PHONE_NUMBER`
6. Set via Supabase CLI:
   ```bash theme={null}
   supabase secrets set TWILIO_ACCOUNT_SID=ACxxxxx
   supabase secrets set TWILIO_AUTH_TOKEN=xxxxx
   supabase secrets set TWILIO_PHONE_NUMBER=+1234567890
   ```

***

## Troubleshooting

### Issue: "Missing required Supabase environment variables"

**Solution:**

1. Check `.env.local` exists
2. Verify variables are set:
   ```bash theme={null}
   echo $VITE_SUPABASE_URL
   echo $VITE_SUPABASE_PUBLISHABLE_KEY
   ```
3. Restart development server
4. Check for typos in variable names

### Issue: "Invalid Supabase URL"

**Solution:**

1. Verify URL format: `https://{project-ref}.supabase.co`
2. Check for trailing slashes (remove if present)
3. Ensure HTTPS (not HTTP)

### Issue: "Service role key in client code"

**Solution:**

1. Remove `SUPABASE_SERVICE_ROLE_KEY` from `.env.local`
2. Only use in test files: `tests/**/*.ts`
3. Never import in client code: `src/**/*.tsx`

### Issue: "Edge function can't access secrets"

**Solution:**

1. Verify secrets are set:
   ```bash theme={null}
   supabase secrets list
   ```
2. Redeploy function after setting secrets:
   ```bash theme={null}
   supabase functions deploy {function-name}
   ```
3. Check secret names match exactly (case-sensitive)

### Issue: "CORS errors in development"

**Solution:**

1. Verify `VITE_SUPABASE_URL` points to correct project
2. Check Supabase project CORS settings
3. Ensure edge functions include CORS headers

***

## Production Deployment

### Vercel Environment Variables

Vercel project environment variables are intentionally not used for this repo's frontend build/runtime configuration as of 2026-04-20.

* Public client config values are defined at build time in `vite.config.ts`.
* `vercel.json` must not contain `env` or `build.env` keys.
* CI enforces this via `npm run check:no-vercel-env-config`.

Do not reintroduce project-level Vercel env variables without explicit platform approval and an updated migration document.

### Supabase Edge Function Secrets

1. Set secrets via Supabase CLI (e.g. Twilio, Entra, Gmail):
   ```bash theme={null}
   supabase secrets set TWILIO_ACCOUNT_SID=ACxxxxx --project-ref {project-ref}
   supabase secrets set ENTRA_CLIENT_SECRET=xxxxx --project-ref {project-ref}
   ```
2. Or via Supabase Dashboard → Project Settings → Edge Functions → Secrets

***

## Related Documentation

* **Edge Functions:** `docs/integrations/EDGE_FUNCTIONS.md`
* **Supabase Setup:** `docs/integrations/SUPABASE_SETUP.md`
* **Secrets Management:** `docs/security/SECRETS_MANAGEMENT.md`
* **.env.example:** `.env.example` (template file)

***

## Related Documentation

### Setup & Configuration

* [PWA Setup](./PWA_SETUP.md) - PWA configuration and environment requirements
* [Setup Quick Start](./SETUP_QUICK_START.md) - First-time developer setup including environment variables

### Development Guides

* [Development Quick Reference](./DEVELOPMENT_QUICK_REFERENCE.md) - Common development tasks

***

**Document Owner:** Platform Team\
**Review Frequency:** Quarterly
