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

# Environment Variables

> Complete reference of all environment variables across the platform

Complete reference of all environment variables used across the Soundcheck platform.

***

## Mobile App

Located in `.env` at the project root.

### Required

| Variable | Description | Example |
| - | - | - |
| `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk publishable key | `pk_test_...` |

### Optional

| Variable | Description | Default |
| - | - | - |
| `EXPO_PUBLIC_API_URL` | API base URL | `https://api.soundchecklive.com` |
| `EXPO_PUBLIC_POSTHOG_API_KEY` | PostHog analytics key | - |
| `EXPO_PUBLIC_POSTHOG_HOST` | PostHog host URL | `https://us.i.posthog.com` |
| `EXPO_PUBLIC_GOOGLE_MAPS_API_KEY` | Google Maps Places key for mobile location autocomplete | - |
| `EXPO_PUBLIC_GOOGLE_PLACES_API_KEY` | Alias for mobile Places key (same value as Maps key) | - |

### Example `.env`

```bash theme={null}
# Required
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_abc123

# Optional - for local development
EXPO_PUBLIC_API_URL=http://localhost:4000
EXPO_PUBLIC_GOOGLE_MAPS_API_KEY=AIza...
# EXPO_PUBLIC_GOOGLE_PLACES_API_KEY=AIza...
```

### EAS environment variables (TestFlight / App Store)

Local `.env` values are **not** used for EAS production builds. Set the same `EXPO_PUBLIC_*` keys in the Expo project for each EAS environment (`production`, `preview`, `development`):

```bash theme={null}
npx eas env:list --environment production
npx eas env:create production --name EXPO_PUBLIC_GOOGLE_MAPS_API_KEY --value "AIza..." --visibility sensitive --type string --scope project
```

See [Mobile Deployment](https://docs.soundchecklive.io/engineering/mobile/deployment.md#eas-environment-variables) for Google Cloud key restrictions.

***

## Web App

Located in `web/.env.local`.

### Required

| Variable | Description | Example |
| - | - | - |
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk publishable key | `pk_test_...` |
| `CLERK_SECRET_KEY` | Clerk secret key (server-side) | `sk_test_...` |
| `NEXT_PUBLIC_API_URL` | API base URL (use `/api-proxy/api/v1` for local dev proxy) | `/api-proxy/api/v1` |

### Optional

| Variable | Description | Default |
| - | - | - |
| `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | Google Maps API key | - |
| `NEXT_PUBLIC_GTM_ID` | Google Tag Manager ID | - |
| `NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME` | Cloudinary cloud name | - |
| `NEXT_PUBLIC_TINY_MCE_API_KEY` | TinyMCE editor key | - |
| `NEXT_PUBLIC_SPOTIFY_CLIENT_ID` | Spotify integration | - |
| `SPOTIFY_CLIENT_SECRET` | Spotify secret (server-side) | - |
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog analytics key | - |
| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog host URL | `https://t.soundchecklive.io` |
| `NEXT_PUBLIC_GOOGLE_PICKER_CLIENT_ID` | Google OAuth web client ID for the one-time Google Drive pick on the "Bring in your past events" step and Settings → Imports (`drive.file` scope). Its numeric prefix is the Cloud project number, used as the Picker app ID. The Google Drive tile is hidden unless this and the API key are both set | - |
| `NEXT_PUBLIC_GOOGLE_PICKER_API_KEY` | Browser API key with the Google Picker API enabled, restricted to the web origins | - |
| `NEXT_PUBLIC_DROPBOX_APP_KEY` | Dropbox app key for the Dropbox Chooser (direct links). The Dropbox tile is hidden when unset | - |

The Google Drive and Dropbox pickers appear wherever imports are available (the `data-import` flag in the web app; the API accepts owners, admins and gig coordinators). The browser hands the picked files (plus a short-lived Google token) to `POST /api/v1/organizations/{orgId}/imports/remote`; api-events downloads them once and never stores the token.

### Example `.env.local`

```bash theme={null}
# Required
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_abc123
CLERK_SECRET_KEY=sk_test_xyz789

# Local dev proxy (avoids CORS — Next.js proxies /api-proxy/* to BACKEND_API_URL)
NEXT_PUBLIC_API_URL=/api-proxy/api/v1
BACKEND_API_URL=http://localhost:4000

# Optional
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=AIza...
NEXT_PUBLIC_POSTHOG_KEY=phc_...
NEXT_PUBLIC_POSTHOG_HOST=https://t.soundchecklive.io
NEXT_PUBLIC_GOOGLE_PICKER_CLIENT_ID=123456789012-abc.apps.googleusercontent.com
NEXT_PUBLIC_GOOGLE_PICKER_API_KEY=AIza...
NEXT_PUBLIC_DROPBOX_APP_KEY=abc123xyz
```

***

## Backend API (api-events)

Local development runs **Encore** on port **4000**. Variables are passed via `npm run dev:emulator` (see `api-events/README.md` and [api-events operations](https://docs.soundchecklive.io/engineering/backend/api-events-operations.md)).

Shared Postgres, fake-gcs-server, and the Clerk emulator: `npm run dev:infra` (Docker Compose under `dev/docker-compose.yml`).

### Required (local)

| Variable | Description | Example |
| - | - | - |
| `DATABASE_URL` | PostgreSQL connection string | `postgresql://postgres:postgres@localhost:5433/soundcheck?schema=public` |
| `CLERK_SECRET_KEY` | Clerk secret for JWT validation | `sk_test_...` |
| `ENVIRONMENT` | Environment name (`development`, `qa`, `production`) | `development` |

### Optional (local)

| Variable | Description | Default |
| - | - | - |
| `CLERK_API_BASE_URL` | Local Clerk emulator base URL | `http://127.0.0.1:4711` |
| `CLERK_WEBHOOK_SECRET` | Clerk webhook signing secret | Emulator default when unset locally |
| `POSTHOG_API_KEY` | PostHog project API key (analytics no-op when unset) | *(empty)* |
| `POSTHOG_HOST` | PostHog ingestion endpoint | `https://us.i.posthog.com` |
| `TELEMETRY_DEV_API_KEY` | telemetry.dev ingest key for MCP server spans (no-op when unset); see [MCP server tracing](https://docs.soundchecklive.io/engineering/backend/integrations/mcp-server.md) | *(empty)* |
| `GCS_BUCKET` | App file bucket. Set: api-events stores files in GCS; unset: Azure Blob Storage | `app-files` (local) |
| `GCS_EMULATOR_HOST` | fake-gcs-server base URL; api-events creates the bucket and signs with a throwaway key | `http://127.0.0.1:4443` (local) |
| `AZURE_STORAGE_USE_EMULATOR` | Use Azurite for Azure blob storage (only when `GCS_BUCKET` is unset) | *(unset)* |
| `ENTITLEMENTS_ENFORCE` | Plan entitlement kill switch. `off` logs every below-tier call and lets it through; anything else follows each key's `enforcement` in `entitlements/registry.json` | follows the registry |
| `TWILIO_API_KEY_SID` / `TWILIO_API_KEY_SECRET` | Conversations REST + JWT signing | — |
| `TWILIO_CONVERSATIONS_SERVICE_SID` | Dedicated Conversations Service | — |
| `TWILIO_GROUPCHAT_NUMBERS` | SMS proxy pool (comma-separated E.164) | — |
| `TWILIO_WEBHOOK_URL` | Programmable Messaging webhook URL for validating inbound invitation/reminder SMS callbacks | — |
| `TWILIO_CONVERSATIONS_WEBHOOK_URL` | Signature URL for `/webhooks/twilio/conversations` | — |
| `TWILIO_SMS_STATUS_WEBHOOK_URL` | `StatusCallback` for outbound SMS, and the signature URL for `/webhooks/twilio/sms-status` | unset (no callbacks) |
| `TWILIO_CONVERSATIONS_ENDPOINT` | Conversations REST override (emulate.dev only) | unset |
| `GOOGLE_MAPS_SERVER_API_KEY` | Server-only Google Maps/Places key for venue/hotel photo proxy and blob copy (never the browser `NEXT_PUBLIC_*` key). Unset → photo proxy returns 404. On GCP, Terraform creates the key (Places API (New) only) and writes it to `<env>-GoogleMapsServerAPIKey`; see `infra-gcp/README.md` | *(empty)* |

### Signup company lookup

`POST /api/v1/public/onboarding/company-lookup` fills the web and mobile signup from a work email or website. It runs on api-events; the web app no longer holds a context.dev key.

| Variable | Description | Default |
| - | - | - |
| `CONTEXT_DEV_API_KEY` | context.dev key for the brand lookup; unset answers `unavailable` and signup asks by hand | *(empty)* |
| `PRICING_CONTEXTDEV_BASE_URL` | context.dev API base URL (shared with pricing knowledge) | `https://api.context.dev/v1` |
| `PUBLIC_COMPANY_LOOKUP_RATELIMIT_RPS` | Per-IP refill rate for the public lookup (one lookup every 6 s) | `0.1667` |
| `PUBLIC_COMPANY_LOOKUP_RATELIMIT_BURST` | Per-IP burst for the public lookup | `10` |
| `PUBLIC_COMPANY_LOOKUP_RATELIMIT_TRUSTED_PROXY_COUNT` | Trusted `X-Forwarded-For` hops; falls back to `PUBLIC_RATELIMIT_TRUSTED_PROXY_COUNT` | `2` |

### Flight status monitors

The `flight-status-monitor` scheduled job watches gig and project flights that depart in the next 72 hours with context.dev page monitors, for organizations with the `flight-status-monitors` flag on. Without a key or a public `https` `API_BASE_URL` (webhook target), the job does nothing.

| Variable | Description | Default |
| - | - | - |
| `CONTEXT_DEV_API_KEY` | context.dev key with `monitors:write`; starts, reads and deletes monitors and reads the flight page on each change | *(empty)* |
| `PRICING_CONTEXTDEV_BASE_URL` | context.dev API base URL (shared) | `https://api.context.dev/v1` |
| `API_BASE_URL` | Public api-events URL; monitors post to `/webhooks/context-dev/flight-monitors/{monitorId}` | set per environment |

### Pricing knowledge (PRO-423)

Ingestion and lookup for the [pricing knowledge base](https://docs.soundchecklive.io/engineering/backend/pricing-knowledge.md).

| Variable | Description | Default |
| - | - | - |
| `CONTEXT_DEV_API_KEY` | context.dev Answers API key; public-web ingestion is disabled when unset | *(empty)* |
| `PRICING_CONTEXTDEV_ENABLED` | Enables context.dev ingestion (also requires the key) | off |
| `PRICING_CONTEXTDEV_MODE` | `fast` or `ultra` extraction mode | `fast` |
| `PRICING_CONTEXTDEV_BASE_URL` | context.dev API base URL | `https://api.context.dev/v1` |
| `PRICING_CONTEXTDEV_MAX_CREDITS_PER_RUN` | Credit ceiling for one ingest run (10–10000) | `400` |
| `PRICING_CONTEXTDEV_CREDIT_RESERVE` | Credits left unspent as a floor (0–100000) | `100` |
| `PRICING_CONTEXTDEV_TIMEOUT_MS` | Per-request timeout (5000–300000) | `45000` |
| `PRICING_CONTEXTDEV_DEADLINE_MS` | Whole-extraction deadline (1000–300000) | `35000` |
| `PRICING_JEV_ENABLED` | Enables the Jev observation classifier (also requires `AI_GATEWAY_API_KEY`) | off |
| `PRICING_JEV_TIMEOUT_MS` | Jev classifier timeout (1–30000) | `4000` |
| `PUBLIC_PRICING_RATELIMIT_RPS` | Token refill rate for `POST /api/v1/public/pricing/lookup` | `2` |
| `PUBLIC_PRICING_RATELIMIT_BURST` | Bucket size for the same route | `10` |
| `PUBLIC_PRICING_RATELIMIT_TRUSTED_PROXY_COUNT` | Proxy hops when resolving the client IP; falls back to `PUBLIC_RATELIMIT_TRUSTED_PROXY_COUNT` | `2` |

### Lead discovery

"Find leads" on `/leads` → Discovered. context.dev Answers researches events, buyers and RFPs near the org's service area; Jev ranks them. Who sees it is the `lead-discovery` PostHog flag, on the API routes and the web tab alike; there is no separate env switch. Runs need a real `CONTEXT_DEV_API_KEY` (the Terraform `PLACEHOLDER` seed counts as unset). Reuses `CONTEXT_DEV_API_KEY`, `PRICING_CONTEXTDEV_BASE_URL` and `AI_GATEWAY_API_KEY`.

| Variable | Description | Default |
| - | - | - |
| `LEAD_DISCOVERY_MODE` | `fast` or `ultra` Answers mode | `fast` |
| `LEAD_DISCOVERY_MAX_CREDITS_PER_RUN` | Credit ceiling for one run; kinds that do not fit are skipped (10–1000) | `30` |
| `LEAD_DISCOVERY_MAX_RUNS_PER_DAY` | Runs per org in a rolling 24 hours (1–100) | `3` |
| `LEAD_DISCOVERY_STALE_RUN_MINUTES` | A running run, or a queued run already dispatched twice, older than this is marked failed by the `lead-discovery-dispatch` job (1–240; raised to one minute past the claim lease when lower) | `10` |
| `LEAD_DISCOVERY_CONTEXTDEV_TIMEOUT_MS` | Per-request timeout (5000–300000) | `60000` |
| `LEAD_DISCOVERY_CONTEXTDEV_DEADLINE_MS` | Answers research deadline (1000–300000) | `45000` |
| `LEAD_DISCOVERY_JEV_ENABLED` | Jev fit ranking (also requires `AI_GATEWAY_API_KEY`); off falls back to a heuristic | on |
| `LEAD_DISCOVERY_JEV_TIMEOUT_MS` | Jev timeout per prospect (1–30000) | `4000` |
| `LEAD_DISCOVERY_JEV_CONCURRENCY` | Parallel Jev calls per run (1–16) | `4` |

Event-chat production setup: [event chats](https://docs.soundchecklive.io/engineering/backend/event-chats). Full provider secret inventory: [api-events operations](https://docs.soundchecklive.io/engineering/backend/api-events-operations.md).

### Example local shell exports

```bash theme={null}
export DATABASE_URL=postgresql://postgres:postgres@localhost:5433/soundcheck?schema=public
export CLERK_SECRET_KEY=sk_test_xyz789
export ENVIRONMENT=development
export CLERK_API_BASE_URL=http://127.0.0.1:4711
```

Prefer `cd api-events && npm run dev:emulator` — it wires the standard local env.

***

## Infrastructure (Terraform)

Passed via `terraform.tfvars` or CI/CD secrets.

### Per-Environment Variables

| Variable | Description |
| - | - |
| `subscription_id` | Azure subscription ID |
| `tenant_id` | Azure AD tenant ID |
| `client_id` | Service principal client ID |
| `client_secret` | Service principal secret |

### Configurable Settings

| Variable | Description | Default |
| - | - | - |
| `app_service_plan_sku` | App Service pricing tier | `B1` (dev) |
| `database_sku` | PostgreSQL pricing tier | `B_Standard_B1ms` |
| `sentry_dsn` | Sentry DSN for monitoring | - |
| `qb_client_id` | QuickBooks OAuth app client ID | - |
| `qb_client_secret` | QuickBooks OAuth app client secret | - |
| `credential_encryption_key` | AES key for QB OAuth token encryption | - |

***

## CI/CD (GitHub Actions)

Stored in GitHub repository secrets.

### Mobile CI/CD

| Secret | Description |
| - | - |
| `EXPO_TOKEN` | Expo access token |
| `TESTFLIGHT_GROUPS` | TestFlight group names (optional) |
| `GOOGLE_SERVICE_ACCOUNT_JSON` | Play Store credentials (optional) |

### Web CI/CD

| Secret | Description |
| - | - |
| `NEXT_PUBLIC_API_URL` | API URL for builds |
| `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` | Clerk key |
| `CLERK_SECRET_KEY` | Clerk secret |
| All `NEXT_PUBLIC_*` variables | Passed to build |

### API CI/CD

| Secret | Description |
| - | - |
| `AZURE_CREDENTIALS` | Azure service principal JSON |
| `ACR_USERNAME` | Container registry username |
| `ACR_PASSWORD` | Container registry password |

### Infrastructure CI/CD

| Secret | Description |
| - | - |
| `ARM_CLIENT_ID` | Azure client ID |
| `ARM_CLIENT_SECRET` | Azure client secret |
| `ARM_SUBSCRIPTION_ID` | Azure subscription |
| `ARM_TENANT_ID` | Azure tenant |
| `TF_VAR_sentry_dsn` | Sentry DSN |

***

## Security Best Practices

> [!WARNING]
>
> Never commit secrets to version control. Use `.env.example` files as templates.

### Do's

* ✅ Use `.env.example` with placeholder values
* ✅ Store production secrets in Key Vault or GitHub Secrets
* ✅ Use different keys per environment
* ✅ Rotate secrets regularly

### Don'ts

* ❌ Commit `.env` files with real values
* ❌ Log environment variables
* ❌ Share secrets via Slack/email
* ❌ Use production secrets in development

***

## Quick Setup

> [tab] Mobile
>
> ```bash theme={null}
> cp .env.example .env
> # Edit .env with your Clerk key
> ```

> [tab] Web
>
> ```bash theme={null}
> cd web
> cp .env.example .env.local
> # Edit .env.local with your keys
> ```

> [tab] api-events
>
> ```bash theme={null}
> npm run dev:infra
> cd api-events && npm run db:migrate
> cd api-events && npm run dev:emulator
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.