Environment Variable Inventory
Executive Overview — all summaries for decision-makers.
No secrets in this document. Set values only in Render Dashboard / Azure / Cloudflare.
Production — Backend (mqa-backend)
| Variable | Required | Description | Source |
|---|---|---|---|
DATABASE_URL | ✅ | PostgreSQL connection string | Render Postgres (auto) |
NODE_ENV | ✅ | production | render.yaml |
PORT | ✅ | 3000 | render.yaml |
JWT_SECRET | ✅ | JWT signing (min. 32 characters) | Render generate |
CORS_ORIGIN | ✅ | https://portal.mqa.group | render.yaml |
FRONTEND_URL | ✅ | https://portal.mqa.group | render.yaml |
CRON_SECRET | ✅ | Header x-cron-secret for /api/jobs/run | Render generate |
SKIP_OAUTH_VALIDATION | ✅ | Must be false in production | render.yaml |
OAUTH_CLIENT_ID | ✅ | Azure App Registration | Azure Portal |
OAUTH_CLIENT_SECRET | ✅ | Azure App Secret | Azure Portal |
OAUTH_TENANT_ID | ✅ | Azure Tenant ID | Azure Portal |
OAUTH_REDIRECT_URI | ✅ | https://api.mqa.group/api/auth/callback | Azure + Render |
ANTHROPIC_API_KEY | ✅ | Claude API | Anthropic Console — see AI-SERVICES |
OPENROUTER_API_KEY | ⚪ | Alternative LLM route | OpenRouter — see AI-SERVICES |
RESEND_API_KEY | ⚪ | Email via Resend | Resend — see RESEND |
MICROSOFT_SENDER_EMAIL | ⚪ | Graph email sender | Azure |
R2_ACCOUNT_ID | ✅ | Cloudflare R2 account | Cloudflare (MQA org) |
R2_ACCESS_KEY_ID | ✅ | R2 API key | Cloudflare |
R2_SECRET_ACCESS_KEY | ✅ | R2 API secret | Cloudflare |
R2_BUCKET_NAME | ✅ | e.g. mqa-media | Cloudflare |
R2_PUBLIC_URL | ✅ | Public R2 CDN URL | Cloudflare |
ADMIN_MAINTENANCE_SECRET | ⚪ | /api/admin/maintenance/* | openssl rand |
EMAIL_WEBHOOK_SECRET | ⚪ | /api/email-tracking/delivered | openssl rand |
OPENSANCTIONS_API_KEY | ⚪ | KYC screening | OpenSanctions |
HIDE_PORTAL_DEMO_CONTENT | ⚪ | Hide demo content | true in prod |
Production — Frontend (mqa-frontend)
| Variable | Required | Description |
|---|---|---|
VITE_API_BASE_URL | ✅ | https://api.mqa.group/api |
VITE_APP_ENV | ✅ | production |
VITE_DOCS_URL | ✅ | https://docs.mqa.group (external VitePress site) |
NODE_VERSION | ✅ | 20 |
Never put secrets under VITE_* — they end up in the browser bundle.
Production — Developer Docs (mqa-docs)
| Variable | Required | Description |
|---|---|---|
DOCS_PASSWORD | ✅ | HTTP Basic Auth password for docs.mqa.group — site is public if unset |
DOCS_USERNAME | ⚪ | Basic Auth username (default mqa) |
PORT | ✅ | Set by Render automatically |
NODE_VERSION | ✅ | 20 |
Service runs apps/docs-site/server.mjs after VitePress build. Share username/password with MANAGEMENT+ / auditors as needed.
Staging (planned)
Same keys as production, different values:
CORS_ORIGIN/FRONTEND_URL→https://staging.portal.mqa.groupVITE_API_BASE_URL→https://staging.api.mqa.group/api- Own
DATABASE_URL,JWT_SECRET, OAuth app
Local Development
| File | Git |
|---|---|
backend/.env | ❌ gitignored |
.env.local | ❌ gitignored |
backend/ENV_TEMPLATE.txt | ✅ template |
Rotation
| Secret | Recommended frequency | Trigger |
|---|---|---|
JWT_SECRET | 12 months | Invalidates all sessions |
CRON_SECRET | 12 months | Update Render Cron Job header |
| R2 keys | On account migration | Immediately |
| OAuth secret | 24 months (Azure policy) | Azure Portal |
ADMIN_MAINTENANCE_SECRET | On team change | Manual |
See SECURITY-STATUS.