Environment Variables Guide
This document describes all environment variables used in Petunia, including where to obtain them, who owns them, and how to rotate them.
Quick Start
- Copy
.env.exampleto.env.local - Fill in the required variables (marked [REQUIRED])
- Run
npm run prestartto validate - Start the application with
npm run dev - See
docs/env/VALIDATION_DECISIONS.mdfor current validation strategy (Zod deferred).
Required Variables
These variables must be set for the application to function.
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | Platform Team | Supabase Dashboard → Settings → API | Never (project-specific) | Public |
NEXT_PUBLIC_SUPABASE_ANON_KEY | Platform Team | Supabase Dashboard → Settings → API | Rotate if exposed | Public |
SUPABASE_SERVICE_ROLE_KEY | Platform Team | Supabase Dashboard → Settings → API | Every 90 days | Secret |
NEXT_PUBLIC_SITE_URL | Platform Team | Your deployment URL | Never | Public |
DATABASE_URL | Platform Team | Supabase Dashboard → Settings → Database | Never (use connection pooling URL) | Secret |
SESSION_SECRET | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
CSRF_SECRET | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
CREDENTIALS_ENCRYPTION_KEY | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
ADMIN_API_KEY | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
INTERNAL_API_KEY | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
CRON_SECRET | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
Optional Variables
These variables enable additional features but are not required for core functionality.
Core Application
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
NODE_ENV | Platform Team | Set to development, production, or test | Never | Public |
NEXT_PUBLIC_API_URL | Platform Team | Usually ${NEXT_PUBLIC_SITE_URL}/api | Never | Public |
NEXT_PUBLIC_WEBSOCKET_URL | Platform Team | WebSocket endpoint URL | Never | Public |
NEXT_PUBLIC_WEBSOCKET_PATH | Platform Team | WebSocket path (default: /api/socketio) | Never | Public |
DIRECT_DATABASE_URL | Platform Team | Direct Postgres connection (if different from DATABASE_URL) | Never | Secret |
Authentication
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
GOOGLE_CLIENT_ID | Integrations Team | Google Cloud Console → APIs & Services → Credentials | Never | Public |
GOOGLE_CLIENT_SECRET | Integrations Team | Google Cloud Console → APIs & Services → Credentials | Every 180 days | Secret |
AUTH_OTP_EXPIRY | Platform Team | OTP expiry in seconds (default: 300) | Never | Public |
EMAIL_VERIFICATION_EXPIRY_HOURS | Platform Team | Email verification link expiry (default: 24) | Never | Public |
PASSWORD_RESET_EXPIRY_MINUTES | Platform Team | Password reset link expiry (default: 30) | Never | Public |
TOTP_WINDOW | Platform Team | TOTP time window for 2FA (default: 1) | Never | Public |
Caching & Knowledge Base
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
UPSTASH_REDIS_REST_URL | Platform Team | Upstash Dashboard → Redis → REST API | Never | Public |
UPSTASH_REDIS_REST_TOKEN | Platform Team | Upstash Dashboard → Redis → REST API | Every 90 days | Secret |
UPSTASH_VECTOR_REST_URL | AI Team | Upstash Dashboard → Vector → REST API | Never | Public |
UPSTASH_VECTOR_REST_TOKEN | AI Team | Upstash Dashboard → Vector → REST API | Every 90 days | Secret |
REDIS_URL | Platform Team | Self-hosted Redis connection string | Never | Secret |
Email Configuration
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
GMAIL_USER | Platform Team | Gmail account email | Never | Public |
GMAIL_APP_PASSWORD | Platform Team | Google Account → Security → App Passwords | Every 180 days | Secret |
EMAIL_FROM | Platform Team | Sender email address | Never | Public |
Voice Services
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
CARTESIA_API_KEY | Voice Team | Cartesia Dashboard → API Keys | Every 90 days | Secret |
CARTESIA_VOICE_ID | Voice Team | Cartesia Dashboard → Voices | Never | Secret |
ELEVENLABS_API_KEY | Voice Team | ElevenLabs Dashboard → Profile → API Key | Every 90 days | Secret |
NEXT_PUBLIC_ELEVENLABS_VOICE_ID | Voice Team | ElevenLabs Dashboard → Voices | Never | Public |
RETELL_API_KEY | Voice Team | Retell Dashboard → API Keys | Every 90 days | Secret |
NEXT_PUBLIC_RETELL_AGENT_ID | Voice Team | Retell Dashboard → Agents | Never | Public |
NEXT_PUBLIC_RETELL_VOICE_ID | Voice Team | Retell Dashboard → Voices | Never | Public |
TWILIO_ACCOUNT_SID | Voice Team | Twilio Console → Account Info | Never | Public |
TWILIO_AUTH_TOKEN | Voice Team | Twilio Console → Account Info | Every 180 days | Secret |
TWILIO_PHONE_NUMBER | Voice Team | Twilio Console → Phone Numbers | Never | Public |
RINGCENTRAL_JWT_TOKEN | Voice Team | RingCentral Developer Portal → JWT | Every 90 days | Secret |
Third-Party Integrations
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
YELP_CLIENT_ID | Integrations Team | Yelp Developer Portal → OAuth App Settings | Never | Public |
YELP_CLIENT_SECRET | Integrations Team | Yelp Developer Portal → OAuth App Settings | Every 180 days | Secret |
YELP_WEBHOOK_SECRET | Integrations Team | Yelp Developer Portal → Webhook Settings | When endpoint changes | Secret |
NEXT_PUBLIC_USE_REAL_YELP_API | Integrations Team | Set to true for production | Never | Public |
YELP_USE_WEBHOOKS | Integrations Team | Enable webhook-based sync | Never | Public |
YELP_USE_PERIODIC_SYNC | Integrations Team | Enable periodic sync | Never | Public |
YELP_SYNC_INTERVAL_MINUTES | Integrations Team | Sync interval (default: 15) | Never | Public |
Multi-Channel Communications
Twilio (SMS)
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
TWILIO_ACCOUNT_SID | Integrations Team | Twilio Console → Account Info | Never | Public |
TWILIO_AUTH_TOKEN | Integrations Team | Twilio Console → Account Info | Every 180 days | Secret |
TWILIO_PHONE_NUMBER | Integrations Team | Twilio Console → Phone Numbers | Never | Public |
TWILIO_VERIFY_SERVICE_SID | Integrations Team | Twilio Console → Verify → Services | Never | Public |
TWILIO_WEBHOOK_URL | Integrations Team | Defaults to ${NEXT_PUBLIC_SITE_URL}/api/webhooks/twilio | Never | Public |
Facebook Integration
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
NEXT_PUBLIC_FACEBOOK_APP_ID | Integrations Team | Facebook Developer Console → App Settings | Never | Public |
FACEBOOK_CLIENT_SECRET | Integrations Team | Facebook Developer Console → App Settings → App Secret | Every 180 days | Secret |
FACEBOOK_WEBHOOK_VERIFY_TOKEN | Integrations Team | Custom token you create for webhook verification | When endpoint changes | Secret |
Google Business Profile
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
NEXT_PUBLIC_GOOGLE_CLIENT_ID | Integrations Team | Google Cloud Console → APIs & Credentials | Never | Public |
GOOGLE_CLIENT_SECRET | Integrations Team | Google Cloud Console → APIs & Credentials | Every 180 days | Secret |
GOOGLE_MAPS_API_KEY | Integrations Team | Google Cloud Console → APIs & Credentials → Maps API Key | Every 180 days | Secret |
GOOGLE_PUBSUB_PROJECT_ID | Integrations Team | Google Cloud Console → Project Settings | Never | Public |
GOOGLE_PUBSUB_SUBSCRIPTION | Integrations Team | Format: projects/{project}/subscriptions/{subscription} | Never | Public |
GOOGLE_SERVICE_ACCOUNT_KEY | Integrations Team | Google Cloud Console → IAM → Service Accounts | Every 365 days | Secret |
AI Services
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
ANTHROPIC_API_KEY | AI Team | Anthropic Console → API Keys | Every 90 days | Secret |
PINECONE_API_KEY | AI Team | Pinecone Console → API Keys | Every 180 days | Secret |
PINECONE_ENVIRONMENT | AI Team | Pinecone Console → Environment | Never | Public |
PINECONE_INDEX_NAME | AI Team | Pinecone Console → Indexes | Never | Public |
Forbidden in all environments:
NEXT_PUBLIC_INTERNAL_API_KEY,NEXT_PUBLIC_ELEVENLABS_API_KEY(use server-only equivalents).
Security & Encryption
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
NEXT_PUBLIC_WS_ENCRYPTION | Platform Team | Set to true to enable | Never | Public |
WS_ENCRYPTION_KEY | Platform Team | Generate with openssl rand -hex 32 | Every 90 days | Secret |
WS_SIGNING_KEY | Platform Team | Generate with openssl rand -hex 32 | Every 90 days | Secret |
NEXT_PUBLIC_WS_SIGN_MESSAGES | Platform Team | Set to true to enable | Never | Public |
ADMIN_API_KEY | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
INTERNAL_API_KEY | Platform Team | Generate with openssl rand -base64 32 | Every 90 days | Secret |
Feature Flags
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
FEATURE_FLAGS_PRESET | Product Team | Set to launch, full, or minimal | Never | Public |
FEATURE_FLAGS_JSON | Product Team | JSON configuration object | Never | Public |
FEATURE_AUTH_SIGNUP | Product Team | Set to true to enable | Never | Public |
FEATURE_ONBOARDING | Product Team | Set to true to enable | Never | Public |
FEATURE_OAUTH | Product Team | Set to true to enable | Never | Public |
FEATURE_CANONICAL_PID_DASHBOARD | Product Team | Set to true to enable (default: true) | Never | Public |
FEATURE_AI_CHAT | Product Team | Set to true to enable | Never | Public |
FEATURE_FILE_UPLOADS | Product Team | Set to true to enable | Never | Public |
FEATURE_WEBHOOKS | Product Team | Set to true to enable | Never | Public |
Development Settings
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
USE_DEMO_SERVICES | Development Team | Set to true for development | Never | Public |
BYPASS_AUTH | Development Team | DANGER: Only for local development | Never | Public |
NEXT_PUBLIC_SKIP_AUTH | Development Team | DANGER: Only for local development | Never | Public |
FORCE_DEMO_LOGIN | Development Team | DANGER: Only for local development | Never | Public |
SKIP_WEBSOCKET_AUTH | Development Team | DANGER: Only for local development | Never | Public |
NEXT_PUBLIC_ENABLE_CLOUD_THEME | Development Team | Set to true to enable cloud theme | Never | Public |
LOG_LEVEL | Development Team | Set to debug, info, warn, or error | Never | Public |
Legacy note: The old
USE_MOCK_SERVICESflag has been removed. SetUSE_DEMO_SERVICES=trueanywhere you previously relied on the mock stack.
ML/AI Features
| Variable | Owner | Where to Get | Rotation Period | Security Level |
|---|---|---|---|---|
ENABLE_ML_ADAPTIVE_LEARNING | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_BEHAVIORAL_ANALYSIS | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_TIMING_OPTIMIZATION | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_PYTORCH_MODEL | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_REALTIME_INTELLIGENCE | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_CONTINUOUS_LEARNING | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_BATCH_TRAINING | AI Team | Set to true to enable | Never | Public |
ENABLE_ML_AUTO_ANALYSIS | AI Team | Set to true to enable | Never | Public |
ML_ADAPTIVE_LEARNING_ROLLOUT | AI Team | Percentage of users (0-100) | Never | Public |
ML_BEHAVIORAL_ANALYSIS_ROLLOUT | AI Team | Percentage of users (0-100) | Never | Public |
ML_TIMING_OPTIMIZATION_ROLLOUT | AI Team | Percentage of users (0-100) | Never | Public |
ML_MAX_CONCURRENT_REQUESTS | AI Team | Max concurrent requests (default: 10) | Never | Public |
ML_REQUEST_TIMEOUT_MS | AI Team | Request timeout in milliseconds (default: 5000) | Never | Public |
ML_CACHE_TTL_SECONDS | AI Team | Cache TTL in seconds (default: 300) | Never | Public |
ML_SERVICE_URL | AI Team | Python ML service URL | Never | Public |
ML_SERVICE_API_KEY | AI Team | ML service API key | Every 90 days | Secret |
Rotation Procedures
Rotating Supabase Service Role Key
- Go to Supabase Dashboard → Settings → API
- Click "Reset service role key"
- Update
SUPABASE_SERVICE_ROLE_KEYin Vercel - Redeploy application
- Verify all services using service role are working
WARNING: This will temporarily break API calls until redeployed.
Rotating Session Secrets
- Generate new secret:
openssl rand -base64 32 - Update
SESSION_SECRETin Vercel - WARNING: This will invalidate all existing sessions
- Redeploy application
- Monitor for user complaints about being logged out
Best Practice: Rotate these during low-traffic periods and communicate to users.
Rotating WebSocket Encryption Keys
- Generate new keys:
openssl rand -hex 32 # For WS_ENCRYPTION_KEY openssl rand -hex 32 # For WS_SIGNING_KEY - Update both keys simultaneously in Vercel
- Redeploy application
- WARNING: Active WebSocket connections will be dropped
- Monitor WebSocket connection health
Rotating Third-Party API Keys
- Generate new key in provider dashboard
- Test new key in staging environment
- Update key in Vercel production environment
- Monitor API usage for errors
- Revoke old key after 24 hours
Providers to rotate regularly:
- Cartesia API Key (every 90 days)
- ElevenLabs API Key (every 90 days)
- Retell API Key (every 90 days)
- Anthropic API Key (every 90 days)
- Yelp Client Secret (every 180 days)
- Google Client Secret (every 180 days)
- Twilio Auth Token (every 180 days)
Rotating Admin API Keys
- Generate new keys:
openssl rand -base64 32 # For ADMIN_API_KEY openssl rand -base64 32 # For INTERNAL_API_KEY - Update keys in Vercel
- Update any internal services using these keys
- Redeploy application
- Verify admin endpoints still work
- Revoke old keys
Rotating Gmail App Password
- Go to Google Account → Security → App Passwords
- Revoke old password
- Generate new app password
- Update
GMAIL_APP_PASSWORDin Vercel - Test email sending functionality
- Verify no errors in logs
Security Best Practices
Secret Management
- Never commit secrets to version control
- Use Vercel environment variables for production
- Rotate secrets regularly according to schedule above
- Use different keys for development, staging, and production
- Monitor for exposed secrets using GitHub secret scanning
- Limit key permissions to minimum required scope
Key Generation
Always use cryptographically secure random generators:
# For NextAuth and session secrets (32+ characters)
openssl rand -base64 32
# For WebSocket encryption keys (32 bytes hex)
openssl rand -hex 32
# For admin API keys
openssl rand -base64 32
Never use:
- Predictable strings
- Dictionary words
- Incrementing numbers
- Previously leaked keys
Environment Separation
Maintain separate keys for each environment:
| Environment | Purpose | Key Rotation |
|---|---|---|
| Development | Local development with demo services | Annually |
| Staging | Pre-production testing with real APIs | Every 180 days |
| Production | Live user-facing application | Every 90 days |
Never use production keys in development or staging.
Access Control
Limit who can access secrets:
| Role | Access Level |
|---|---|
| Developers | Development keys only |
| DevOps | All environments (read-only) |
| Platform Team | All environments (full access) |
| Admins | Production (full access) |
Use Vercel Teams and RBAC to enforce access control.
Troubleshooting
"Missing required variable" error
Run npm run prestart to see which variables are missing. Copy .env.example to .env.local and fill in the values.
"Invalid URL format" error
Ensure URLs start with http:// or https://. For Supabase URL, always use https://.
"Session secret too short" error
SESSION_SECRET must be at least 32 characters. Generate a new one with openssl rand -base64 32.
"Database connection failed" error
Check DATABASE_URL format:
postgresql://user:password@host:port/database
For Supabase, use the connection pooling URL (includes port 6543).
"Supabase anon key invalid" error
Verify you copied the correct key from Supabase Dashboard → Settings → API. The anon key is the "public" key, not the service role key.
"NextAuth callback error" error
"WebSocket connection refused" error
Check:
NEXT_PUBLIC_WEBSOCKET_URLis correctNEXT_PUBLIC_WEBSOCKET_PATHmatches server path- WebSocket server is running (port 3001 by default)
"Yelp OAuth redirect mismatch" error
Verify redirect URI in Yelp Developer Portal matches:
${NEXT_PUBLIC_SITE_URL}/api/auth/callback/yelp
"Voice service API key invalid" error
Check:
- Key is not expired
- Key has correct permissions
- Account has sufficient credits
- API endpoint URL is correct
Environment Variables Not Loading
If variables aren't loading in your application:
- Restart development server after changing
.env.local - Check file location: Must be in project root
- Check file name: Must be exactly
.env.local(not.env.local.txt) - Check syntax: No quotes needed, no spaces around
= - Check caching: Clear Next.js cache with
rm -rf .next
Validation
Manual Validation
Run the prestart script to validate environment variables:
npm run prestart
This checks:
- All required variables are present
- URLs are valid format
- Secrets meet minimum length requirements
- Numeric values are within valid ranges
Automated Validation
The application validates environment variables at startup:
- Required variables check: Fails if missing
- Format validation: Warns if invalid format
- Security checks: Warns if using default/weak values
- Feature flag validation: Ensures valid preset or JSON
CI/CD Validation
In CI/CD pipelines, validate before deployment:
# In .github/workflows/deploy.yml
- name: Validate Environment
run: npm run prestart
env:
# Pull from Vercel or GitHub Secrets
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
# ... other variables
Contact
For questions about environment variables, contact:
| Category | Team | Contact |
|---|---|---|
| Platform variables (Supabase, Database, Sessions) | Platform Team | support@gardenpatch.xyz |
| Integration variables (Yelp, Google, Facebook) | Integrations Team | support@gardenpatch.xyz |
| AI/Voice variables (Cartesia, ElevenLabs, Retell, Anthropic) | AI/Voice Team | support@gardenpatch.xyz |
| Security variables (Encryption keys, Admin keys) | Security Team | support@gardenpatch.xyz |
| Development variables (Demo services, Feature flags) | Development Team | support@gardenpatch.xyz |
Multi-Channel Integration Setup
Twilio SMS Setup
- Create Twilio Account: Go to Twilio Console
- Get Account Credentials:
- Account SID and Auth Token from Account Info
- Purchase a phone number with SMS capability
- Configure Webhook:
- Go to Phone Numbers → Manage → Active Numbers
- Select your number
- Under "Messaging", set webhook URL to:
https://your-domain.com/api/webhooks/twilio - Method: HTTP POST
- Set Environment Variables:
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx TWILIO_AUTH_TOKEN=your_auth_token TWILIO_PHONE_NUMBER=+15551234567
Facebook Integration Setup
- Create Facebook App: Go to Facebook Developers
- Add Products:
- Facebook Login
- Messenger
- Configure OAuth:
- Settings → Basic → Add Platform → Website
- Valid OAuth Redirect URIs:
https://your-domain.com/settings/connections/facebook/callback
- Configure Webhooks:
- Messenger → Settings → Webhooks
- Callback URL:
https://your-domain.com/api/webhooks/facebook - Verify Token: Your custom
FACEBOOK_WEBHOOK_VERIFY_TOKEN - Subscribe to: messages, messaging_postbacks
- Set Environment Variables:
NEXT_PUBLIC_FACEBOOK_APP_ID=your_app_id FACEBOOK_CLIENT_SECRET=your_app_secret FACEBOOK_WEBHOOK_VERIFY_TOKEN=your_custom_verify_token
Google Business Profile Setup
-
Create Google Cloud Project: Go to Google Cloud Console
-
Enable APIs:
- My Business Business Information API
- My Business Account Management API
- Cloud Pub/Sub API
-
Create OAuth Credentials:
- APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
- Application type: Web application
- Authorized redirect URIs:
https://your-domain.com/settings/connections/google/callback
-
Configure Pub/Sub (for real-time notifications):
# Create topic gcloud pubsub topics create petunia-google-notifications # Create push subscription gcloud pubsub subscriptions create petunia-google-sub \ --topic=petunia-google-notifications \ --push-endpoint=https://your-domain.com/api/webhooks/google \ --push-auth-service-account=your-sa@project.iam.gserviceaccount.com -
Set Environment Variables:
NEXT_PUBLIC_GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com GOOGLE_CLIENT_SECRET=your_client_secret GOOGLE_PUBSUB_PROJECT_ID=your-gcp-project GOOGLE_PUBSUB_SUBSCRIPTION=projects/your-project/subscriptions/petunia-google-sub
Additional Resources
- Supabase Documentation
- Next.js Environment Variables
- Vercel Environment Variables
- NextAuth.js Configuration
- Yelp Developer Portal
- Google Cloud Console
- Google My Business API
- Google Pub/Sub Documentation
- Facebook Developer Documentation
- Facebook Messenger Platform
- Twilio SMS Webhooks
- Cartesia Documentation
- ElevenLabs Documentation
- Retell Documentation
- Anthropic Documentation
- Pinecone Documentation