# API Key Management Flow Diagrams ## 🏠 Local Development Mode ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ LOCAL DEVELOPMENT β”‚ β”‚ (DEBUG=true) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Developer completes onboarding β”‚ β”œβ”€> Frontend: Save API keys β”‚ └─> POST /api/onboarding/api-keys (gemini, exa, copilotkit) β”‚ β”œβ”€> Backend: Process API keys β”‚ β”‚ β”‚ β”œβ”€> Save to PostgreSQL database β”‚ β”‚ └─> onboarding_sessions (user_id) β”‚ β”‚ └─> api_keys (provider, key) β”‚ β”‚ β”‚ └─> Save to backend/.env file [DEV MODE ONLY] β”‚ β”œβ”€> GEMINI_API_KEY=xxx β”‚ β”œβ”€> EXA_API_KEY=xxx β”‚ └─> COPILOTKIT_API_KEY=xxx β”‚ └─> Frontend: Save CopilotKit to frontend/.env └─> REACT_APP_COPILOTKIT_API_KEY=xxx Developer generates content β”‚ β”œβ”€> Service calls user_api_keys(user_id=None) β”‚ β”‚ β”‚ └─> Detects DEV mode (DEBUG=true) β”‚ └─> Reads from backend/.env file β”‚ └─> Returns all keys β”‚ └─> Content generated using developer's keys └─> All costs β†’ Developer's API account βœ… Advantages: β€’ Quick setup (keys persist in .env) β€’ No database required for basic dev β€’ Single developer = single set of keys β€’ Keys survive server restarts ``` --- ## 🌐 Production Mode (Multi-User) ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ PRODUCTION (VERCEL + RENDER) β”‚ β”‚ (DEBUG=false, DEPLOY_ENV=render) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Alpha Tester A visits https://alwrity-ai.vercel.app β”‚ β”œβ”€> Completes onboarding β”‚ └─> Enters API keys: β”‚ β”œβ”€> GEMINI_API_KEY=tester_a_key β”‚ β”œβ”€> EXA_API_KEY=tester_a_exa β”‚ └─> COPILOTKIT_API_KEY=tester_a_copilot β”‚ β”œβ”€> Frontend: Save API keys β”‚ β”œβ”€> POST /api/onboarding/api-keys (gemini, exa, copilotkit) β”‚ └─> Save to localStorage (CopilotKit) β”‚ └─> Backend: Process API keys β”œβ”€> Save to PostgreSQL database ONLY [PROD MODE] β”‚ └─> onboarding_sessions β”‚ β”œβ”€> user_id = "user_clerk_tester_a" β”‚ └─> api_keys β”‚ β”œβ”€> (session_id, "gemini", "tester_a_key") β”‚ β”œβ”€> (session_id, "exa", "tester_a_exa") β”‚ └─> (session_id, "copilotkit", "tester_a_copilot") β”‚ └─> [SKIP] ❌ Do NOT save to .env file (multi-user conflict!) Alpha Tester A generates blog content β”‚ β”œβ”€> Request to /api/blog/generate β”‚ └─> Headers: Authorization: Bearer β”‚ β”œβ”€> Auth Middleware extracts user_id = "user_clerk_tester_a" β”‚ β”œβ”€> BlogService calls user_api_keys("user_clerk_tester_a") β”‚ β”‚ β”‚ β”œβ”€> Detects PROD mode (DEPLOY_ENV=render) β”‚ β”‚ β”‚ └─> Query database: β”‚ SELECT key FROM api_keys β”‚ WHERE session_id = ( β”‚ SELECT id FROM onboarding_sessions β”‚ WHERE user_id = 'user_clerk_tester_a' β”‚ ) β”‚ └─> Returns: {"gemini": "tester_a_key", "exa": "tester_a_exa"} β”‚ └─> Content generated using Tester A's Gemini key └─> All costs β†’ Tester A's Gemini account ──────────────────────────────────────────────────────────────────────── SIMULTANEOUSLY... Alpha Tester B visits https://alwrity-ai.vercel.app β”‚ β”œβ”€> Completes onboarding β”‚ └─> Enters API keys: β”‚ β”œβ”€> GEMINI_API_KEY=tester_b_key β”‚ β”œβ”€> EXA_API_KEY=tester_b_exa β”‚ └─> COPILOTKIT_API_KEY=tester_b_copilot β”‚ └─> Backend: Save to database └─> onboarding_sessions β”œβ”€> user_id = "user_clerk_tester_b" └─> api_keys β”œβ”€> (session_id, "gemini", "tester_b_key") [SEPARATE!] β”œβ”€> (session_id, "exa", "tester_b_exa") └─> (session_id, "copilotkit", "tester_b_copilot") Alpha Tester B generates blog content β”‚ β”œβ”€> Request to /api/blog/generate β”‚ └─> Headers: Authorization: Bearer β”‚ β”œβ”€> Auth Middleware extracts user_id = "user_clerk_tester_b" β”‚ β”œβ”€> BlogService calls user_api_keys("user_clerk_tester_b") β”‚ β”‚ β”‚ └─> Query database: β”‚ WHERE user_id = 'user_clerk_tester_b' [DIFFERENT!] β”‚ └─> Returns: {"gemini": "tester_b_key", "exa": "tester_b_exa"} β”‚ └─> Content generated using Tester B's Gemini key └─> All costs β†’ Tester B's Gemini account βœ… User Isolation: β€’ Tester A's keys β‰  Tester B's keys β€’ Tester A's costs β‰  Tester B's costs β€’ Completely isolated in database β€’ You (owner) pay nothing! πŸŽ‰ ``` --- ## πŸ”„ Environment Detection Logic ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ENVIRONMENT DETECTION β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ When user_api_keys(user_id) is called: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Check environment variables β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”œβ”€> DEBUG=true OR DEPLOY_ENV=None β”‚ β”‚ β”‚ β”œβ”€> DEVELOPMENT MODE β”‚ β”‚ └─> Read from backend/.env file β”‚ β”‚ └─> os.getenv('GEMINI_API_KEY') β”‚ β”‚ β”‚ └─> Log: "[DEV MODE] Using .env file" β”‚ └─> DEBUG=false AND DEPLOY_ENV=render β”‚ β”œβ”€> PRODUCTION MODE β”‚ └─> Read from database β”‚ └─> SELECT key FROM api_keys WHERE user_id=? β”‚ └─> Log: "[PROD MODE] Using database for user {user_id}" Example configurations: Local Development: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ backend/.env β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ DEBUG=true β”‚ β”‚ GEMINI_API_KEY=dev_key β”‚ β”‚ EXA_API_KEY=dev_exa β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Render Production: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Environment Variables β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ DEBUG=false β”‚ β”‚ DEPLOY_ENV=render β”‚ β”‚ DATABASE_URL=postgresql:// β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ“Š Database Schema Visualization ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ DATABASE SCHEMA β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ onboarding_sessions β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ id (PK) β”‚ user_id (UNIQUE) β”‚ current_stepβ”‚ progress β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1 β”‚ user_clerk_tester_a β”‚ 6 β”‚ 100.0 β”‚ β”‚ 2 β”‚ user_clerk_tester_b β”‚ 6 β”‚ 100.0 β”‚ β”‚ 3 β”‚ user_clerk_tester_c β”‚ 3 β”‚ 50.0 β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ api_keys β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ id (PK) β”‚ session_id β”‚ provider β”‚ key β”‚ β”‚ β”‚ (FK) β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 1 β”‚ 1 β”‚ gemini β”‚ tester_a_gemini_key β”‚ ← Tester A β”‚ 2 β”‚ 1 β”‚ exa β”‚ tester_a_exa_key β”‚ ← Tester A β”‚ 3 β”‚ 1 β”‚ copilotkit β”‚ tester_a_copilot_key β”‚ ← Tester A β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 4 β”‚ 2 β”‚ gemini β”‚ tester_b_gemini_key β”‚ ← Tester B β”‚ 5 β”‚ 2 β”‚ exa β”‚ tester_b_exa_key β”‚ ← Tester B β”‚ 6 β”‚ 2 β”‚ copilotkit β”‚ tester_b_copilot_key β”‚ ← Tester B β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ 7 β”‚ 3 β”‚ gemini β”‚ tester_c_gemini_key β”‚ ← Tester C β”‚ 8 β”‚ 3 β”‚ exa β”‚ tester_c_exa_key β”‚ ← Tester C β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Query to get Tester A's Gemini key: SELECT k.key FROM api_keys k JOIN onboarding_sessions s ON k.session_id = s.id WHERE s.user_id = 'user_clerk_tester_a' AND k.provider = 'gemini' Result: 'tester_a_gemini_key' Query to get Tester B's Gemini key: SELECT k.key FROM api_keys k JOIN onboarding_sessions s ON k.session_id = s.id WHERE s.user_id = 'user_clerk_tester_b' AND k.provider = 'gemini' Result: 'tester_b_gemini_key' [DIFFERENT!] ``` --- ## πŸ” Security & Isolation ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ USER ISOLATION GUARANTEE β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Scenario: Both Tester A and Tester B generate content simultaneously Tester A's Request Thread: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 1. Auth: user_id = "user_clerk_tester_a" β”‚ β”‚ 2. Fetch keys: WHERE user_id = tester_a β”‚ β”‚ 3. Get: gemini_key = "tester_a_key" β”‚ β”‚ 4. Generate with tester_a_key β”‚ β”‚ 5. Response to Tester A β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ↓ [Database] ↑ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ 1. Auth: user_id = "user_clerk_tester_b" β”‚ β”‚ 2. Fetch keys: WHERE user_id = tester_b β”‚ β”‚ 3. Get: gemini_key = "tester_b_key" β”‚ β”‚ 4. Generate with tester_b_key β”‚ β”‚ 5. Response to Tester B β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Tester B's Request Thread: βœ… Guarantees: β€’ Tester A NEVER sees Tester B's keys β€’ Tester B NEVER sees Tester A's keys β€’ Tester A's costs charged to Tester A β€’ Tester B's costs charged to Tester B β€’ Database enforces isolation via user_id β€’ Clerk auth ensures correct user_id ``` --- ## πŸ’° Cost Distribution ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ WHO PAYS FOR WHAT? β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Local Development (You): Your API Keys β†’ Your Costs β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Developer generates 100 blog posts β”‚ β”‚ Uses: GEMINI_API_KEY from .env β”‚ β”‚ Cost: $5.00 β†’ Charged to developer's β”‚ β”‚ Google Cloud account β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Production (Alpha Testers): Their API Keys β†’ Their Costs β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Tester A generates 50 blog posts β”‚ β”‚ Uses: tester_a_gemini_key from database β”‚ β”‚ Cost: $2.50 β†’ Charged to Tester A's β”‚ β”‚ Google Cloud account β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Tester B generates 200 blog posts β”‚ β”‚ Uses: tester_b_gemini_key from database β”‚ β”‚ Cost: $10.00 β†’ Charged to Tester B's β”‚ β”‚ Google Cloud account β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ You (owner) host infrastructure β”‚ β”‚ Render: Free tier / $7/month β”‚ β”‚ Vercel: Free tier β”‚ β”‚ Database: Render free tier β”‚ β”‚ Cost: $0 - $7/month (infrastructure only) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ Total monthly cost for you with 100 alpha testers: Infrastructure: $0 - $7 API usage: $0 (testers pay their own!) ──────────────────────────── Total: $0 - $7/month πŸŽ‰ ``` --- ## 🎯 Summary | Aspect | Local Dev | Production | |--------|-----------|------------| | **Environment** | `DEBUG=true` | `DEPLOY_ENV=render` | | **Key Storage** | `.env` file + DB | Database only | | **Key Retrieval** | `os.getenv()` | Database query | | **User Isolation** | Not needed | Full isolation | | **Cost Bearer** | You (developer) | Each tester | | **Scalability** | 1 developer | Unlimited users | | **Setup Effort** | Low (persist .env) | Low (onboard once) | **Architecture Principle:** > Development convenience with `.env` files, production isolation with database. Best of both worlds! πŸš€