# API Key Management Architecture ## Overview ALwrity supports two deployment modes with different API key management strategies: 1. **Local Development**: API keys stored in `.env` files for convenience 2. **Production (Vercel + Render)**: User-specific API keys stored in database with full user isolation ## Architecture ### 🏠 **Local Development Mode** **Detection:** - `DEBUG=true` in environment variables, OR - `DEPLOY_ENV` is not set **API Key Storage:** - **Backend**: `backend/.env` file - **Frontend**: `frontend/.env` file - **Database**: Also saved for consistency **Flow:** ``` User completes onboarding ↓ API keys saved to database (user-isolated) ↓ API keys ALSO saved to .env files (for convenience) ↓ Backend services read from .env file ↓ Single developer, single set of keys ``` **Advantages:** - ✅ Quick setup for developers - ✅ No need to configure environment for every user - ✅ Keys persist across server restarts --- ### 🌐 **Production Mode (Vercel + Render)** **Detection:** - `DEBUG=false` or not set, AND - `DEPLOY_ENV` is set (e.g., `DEPLOY_ENV=render`) **API Key Storage:** - **Backend**: PostgreSQL database (user-isolated) - **Frontend**: `localStorage` (runtime only) - **NOT in .env files** **Flow:** ``` Alpha Tester A completes onboarding ↓ API keys saved to database with user_id_A ↓ Backend services fetch keys from database when user_id_A makes requests ↓ Multiple users, each with their own keys ↓ Alpha Tester B completes onboarding ↓ API keys saved to database with user_id_B ↓ Backend services fetch keys from database when user_id_B makes requests ``` **Advantages:** - ✅ **Complete user isolation** - User A's keys never conflict with User B's keys - ✅ **Zero cost for you** - Each alpha tester uses their own API keys - ✅ **Secure** - Keys stored encrypted in database - ✅ **Scalable** - Unlimited alpha testers, each with their own keys --- ## Implementation ### **1. Backend: User API Key Context** The `UserAPIKeyContext` class provides user-specific API keys to backend services: ```python from services.user_api_key_context import user_api_keys # In your backend service async def generate_content(user_id: str, prompt: str): # Get user-specific API keys with user_api_keys(user_id) as keys: gemini_key = keys.get('gemini') exa_key = keys.get('exa') # Use keys for this specific user response = await call_gemini_api(gemini_key, prompt) return response ``` **How it works:** - **Development**: Reads from `backend/.env` - **Production**: Fetches from database for the specific `user_id` ### **2. Frontend: CopilotKit Key Management** ```typescript // Frontend automatically handles this: // 1. Saves to localStorage (for runtime use) // 2. In dev: Also saves to frontend/.env // 3. In prod: Only uses localStorage const copilotApiKey = localStorage.getItem('copilotkit_api_key'); ``` ### **3. Environment Variable Detection** **Backend (`backend/.env`):** ```bash # Development DEBUG=true # Production DEBUG=false DEPLOY_ENV=render # or 'railway', 'heroku', etc. ``` **Render Dashboard:** ``` DEBUG=false DEPLOY_ENV=render ``` **Vercel Dashboard:** ``` REACT_APP_API_URL=https://alwrity.onrender.com REACT_APP_BACKEND_URL=https://alwrity.onrender.com ``` --- ## Use Cases ### **Use Case 1: You (Developer) - Local Development** **Setup:** ```bash # backend/.env DEBUG=true GEMINI_API_KEY=your_personal_key EXA_API_KEY=your_personal_key COPILOTKIT_API_KEY=your_personal_key ``` **Behavior:** - You complete onboarding once - Keys saved to both database AND `.env` files - All your local testing uses these keys - No need to re-enter keys --- ### **Use Case 2: Alpha Tester A - Production** **Setup:** - Alpha Tester A visits `https://alwrity-ai.vercel.app` - Goes through onboarding - Enters their own API keys: - `GEMINI_API_KEY=tester_a_gemini_key` - `EXA_API_KEY=tester_a_exa_key` - `COPILOTKIT_API_KEY=tester_a_copilot_key` **Behavior:** - Keys saved to database with `user_id=tester_a_clerk_id` - When Tester A generates content: - Backend fetches `tester_a_gemini_key` from database - Uses Tester A's Gemini quota - All costs charged to Tester A's Gemini account --- ### **Use Case 3: Alpha Tester B - Production (Same Time)** **Setup:** - Alpha Tester B visits `https://alwrity-ai.vercel.app` - Goes through onboarding - Enters their own API keys: - `GEMINI_API_KEY=tester_b_gemini_key` - `EXA_API_KEY=tester_b_exa_key` - `COPILOTKIT_API_KEY=tester_b_copilot_key` **Behavior:** - Keys saved to database with `user_id=tester_b_clerk_id` - When Tester B generates content: - Backend fetches `tester_b_gemini_key` from database - Uses Tester B's Gemini quota - All costs charged to Tester B's Gemini account - **Tester A and Tester B completely isolated** ✅ --- ## Database Schema ```sql -- OnboardingSession: One per user CREATE TABLE onboarding_sessions ( id SERIAL PRIMARY KEY, user_id VARCHAR(255) UNIQUE NOT NULL, -- Clerk user ID current_step INTEGER DEFAULT 1, progress FLOAT DEFAULT 0.0, started_at TIMESTAMP DEFAULT NOW(), completed_at TIMESTAMP ); -- APIKey: Multiple per user (one per provider) CREATE TABLE api_keys ( id SERIAL PRIMARY KEY, session_id INTEGER REFERENCES onboarding_sessions(id), provider VARCHAR(50) NOT NULL, -- 'gemini', 'exa', 'copilotkit' key TEXT NOT NULL, -- Encrypted in production created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(session_id, provider) -- One key per provider per user ); ``` **Isolation:** - Each user has their own `onboarding_session` - Each session has its own set of `api_keys` - Query: `SELECT key FROM api_keys WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = ?)` --- ## Migration Path ### **Current State:** - ❌ All users' keys overwrite the same `.env` file - ❌ Last user's keys are used for all users ### **New State:** - ✅ Development: `.env` file for convenience - ✅ Production: Database per user - ✅ Complete user isolation ### **Code Changes Required:** **Before (BAD - uses global .env):** ```python import os def generate_content(prompt: str): gemini_key = os.getenv('GEMINI_API_KEY') # Same for all users! response = call_gemini_api(gemini_key, prompt) return response ``` **After (GOOD - uses user-specific keys):** ```python from services.user_api_key_context import user_api_keys def generate_content(user_id: str, prompt: str): with user_api_keys(user_id) as keys: gemini_key = keys.get('gemini') # User-specific key! response = call_gemini_api(gemini_key, prompt) return response ``` --- ## Testing ### **Test Local Development:** 1. Set `DEBUG=true` in `backend/.env` 2. Complete onboarding with test keys 3. Check `backend/.env` - should contain keys ✅ 4. Generate content - should use keys from `.env` ✅ ### **Test Production:** 1. Set `DEBUG=false` and `DEPLOY_ENV=render` on Render 2. User A completes onboarding with keys A 3. User B completes onboarding with keys B 4. User A generates content - uses keys A ✅ 5. User B generates content - uses keys B ✅ 6. Check database: ```sql SELECT user_id, provider, key FROM api_keys JOIN onboarding_sessions ON api_keys.session_id = onboarding_sessions.id; ``` Should show separate keys for User A and User B ✅ --- ## Security Considerations ### **Production Enhancements (Future):** 1. **Encrypt API keys** in database using application secret 2. **Rate limiting** per user to prevent abuse 3. **Key validation** before saving 4. **Audit logging** of API key usage 5. **Key rotation** support ### **Current Implementation:** - ✅ Keys stored in database (not in code) - ✅ User isolation via `user_id` - ✅ HTTPS encryption in transit - ⚠️ Keys not encrypted at rest (TODO) --- ## Troubleshooting ### **Issue: "No API key found"** - **Development**: Check `backend/.env` file exists and has keys - **Production**: Check database has keys for this user: ```sql SELECT * FROM api_keys WHERE session_id = (SELECT id FROM onboarding_sessions WHERE user_id = 'user_xxx'); ``` ### **Issue: "Wrong user's keys being used"** - **Cause**: Service not using `UserAPIKeyContext` - **Fix**: Update service to use `user_api_keys(user_id)` context manager ### **Issue: "Keys not saving to .env in development"** - **Cause**: `DEBUG` not set to `true` - **Fix**: Set `DEBUG=true` in `backend/.env` --- ## Summary | Feature | Local Development | Production | |---------|------------------|------------| | **Key Storage** | `.env` files + Database | Database only | | **User Isolation** | Not needed (single user) | Full isolation | | **Cost** | Your API keys | Each user's API keys | | **Convenience** | High (keys persist) | Medium (enter once) | | **Scalability** | 1 developer | Unlimited users | | **Detection** | `DEBUG=true` | `DEPLOY_ENV` set | **Bottom Line:** - 🏠 **Local**: Quick setup, your keys, `.env` convenience - 🌐 **Production**: User isolation, their keys, zero cost for you This architecture ensures: 1. ✅ You can develop locally with convenience 2. ✅ Alpha testers use their own keys (no cost to you) 3. ✅ Complete user isolation in production 4. ✅ Seamless transition between environments