# Free Account Options - Architecture Analysis **Date:** January 14, 2026 **Status:** Planning Phase **Purpose:** Compare two approaches for free user onboarding with limited AI operations --- ## Current System Architecture ### 1. **Account & Plan System** ```python Account Model: - plan (FK to Plan) - credits (Integer, current balance) - status (trial, active, suspended, pending_payment, cancelled) - payment_method - usage_ahrefs_queries (monthly counter) - usage_period_start/end Plan Model: - name, slug, price, billing_cycle - is_internal (hide from public listings) - max_sites, max_keywords, max_users, max_author_profiles - included_credits (monthly allocation) - extra_credit_price - allow_credit_topup - max_ahrefs_queries (monthly limit) ``` ### 2. **AI Configuration System** ```python AIModelConfig (Global - Single Source of Truth): - model_name (e.g., 'gpt-4o-mini', 'hidream-full') - model_type (text/image) - provider (openai, runware, etc.) - is_default (one default per type) - is_active - cost_per_1k_input/output (text models) - credits_per_image (image models) - tokens_per_credit (text models) AISettings (Per-Account Overrides): - account (FK) - integration_type (openai, runware) - config (API keys, settings) - model_preferences (per operation type) - cost_limits (budgets) ``` ### 3. **Credit Tracking System** ```python CreditTransaction: - transaction_type (purchase, subscription, deduction, adjustment) - amount (positive/negative) - balance_after - description, metadata CreditUsageLog (Per AI Operation): - operation_type (clustering, idea_generation, content_generation, image_generation) - credits_used - cost_usd - model_used - tokens_input/output - site (FK for filtering) - related_object_type/id ``` ### 4. **Current Registration Flow** 1. User registers → `RegisterSerializer.create()` 2. If `plan_slug` not provided or = 'free': - Assigns Plan.slug='free' (must exist) - Account.status = 'trial' - Account.credits = plan.included_credits - Creates CreditTransaction (initial allocation) 3. User can perform AI operations until credits exhausted --- ## 📊 Option 1: Individual Free Accounts (Isolated) ### **Concept** Each user gets their own free account with: - Fixed cheaper AI models (GPT-4o mini, Hidream-full) - Low credit allocation (50-100 operations) - Own isolated data/workspace - Ability to upgrade to paid plan ### **Implementation Plan** #### **Step 1: Create Free Plan** ```sql -- Admin action via Django Admin INSERT INTO igny8_plans ( name, slug, price, billing_cycle, is_featured, is_internal, is_active, max_sites, max_users, max_keywords, included_credits, allow_credit_topup, max_ahrefs_queries ) VALUES ( 'Free Starter', 'free', 0.00, 'monthly', false, true, true, 1, -- max_sites: 1 site only 1, -- max_users: owner only 100, -- max_keywords: 100 100, -- included_credits: 100 credits (~50 operations) false, -- No credit topup for free 0 -- No Ahrefs access ); ``` #### **Step 2: Create AI Model Configs (If Not Exist)** ```sql -- GPT-4o Mini (cheaper text model) INSERT INTO igny8_ai_model_config ( model_name, model_type, provider, display_name, is_default, is_active, cost_per_1k_input, cost_per_1k_output, tokens_per_credit, max_tokens, context_window ) VALUES ( 'gpt-4o-mini', 'text', 'openai', 'GPT-4o Mini (Fast & Efficient)', false, true, 0.00015, 0.0006, -- Cheaper than GPT-4 1000, 16384, 128000 ); -- Hidream Full (cheaper image model) INSERT INTO igny8_ai_model_config ( model_name, model_type, provider, display_name, is_default, is_active, credits_per_image, quality_tier, square_size, landscape_size ) VALUES ( 'hidream-full', 'image', 'runware', 'Hidream Full (Standard Quality)', false, true, 1, -- 1 credit per image (cheapest) 'basic', '1024x1024', '1280x768' ); ``` #### **Step 3: Update Registration Logic** ```python # In auth/serializers.py RegisterSerializer.create() # No changes needed! Current logic already handles this: if not plan_slug or plan_slug == 'free': plan = Plan.objects.get(slug='free', is_active=True) account_status = 'trial' initial_credits = plan.included_credits # 100 credits ``` #### **Step 4: Force Free Plan AI Models** **Option A: Global Default (Simplest)** ```sql -- Set GPT-4o Mini and Hidream as defaults UPDATE igny8_ai_model_config SET is_default = false WHERE model_type = 'text'; UPDATE igny8_ai_model_config SET is_default = true WHERE model_name = 'gpt-4o-mini'; UPDATE igny8_ai_model_config SET is_default = false WHERE model_type = 'image'; UPDATE igny8_ai_model_config SET is_default = true WHERE model_name = 'hidream-full'; ``` **Pros:** Zero code changes, all free accounts inherit defaults **Cons:** Affects ALL accounts (paid users too) **Option B: Per-Account AI Settings (Recommended)** ```python # In auth/serializers.py RegisterSerializer.create() # After account creation: if account_status == 'trial': # Free accounts only from igny8_core.modules.system.settings_models import AISettings # Create AI settings for OpenAI (text) AISettings.objects.create( account=account, integration_type='openai', model_preferences={ 'clustering': 'gpt-4o-mini', 'idea_generation': 'gpt-4o-mini', 'content_generation': 'gpt-4o-mini', 'optimization': 'gpt-4o-mini', }, is_active=True ) # Create AI settings for Runware (images) AISettings.objects.create( account=account, integration_type='runware', model_preferences={ 'image_generation': 'hidream-full', }, is_active=True ) ``` **Pros:** Free accounts locked to cheap models, paid accounts unaffected **Cons:** Requires code change in registration flow **Option C: Plan-Level AI Model Configuration** ```python # Add new field to Plan model (migration required) class Plan(models.Model): # ... existing fields ... allowed_text_models = models.JSONField( default=list, help_text="Allowed text AI models (empty = all)" ) allowed_image_models = models.JSONField( default=list, help_text="Allowed image AI models (empty = all)" ) force_default_models = models.BooleanField( default=False, help_text="Force plan defaults, ignore user overrides" ) # Update Free plan: plan = Plan.objects.get(slug='free') plan.allowed_text_models = ['gpt-4o-mini'] plan.allowed_image_models = ['hidream-full'] plan.force_default_models = True plan.save() # In AI operation logic (ai/services.py or similar): def get_ai_model_for_account(account, operation_type): plan = account.plan if plan.force_default_models: if operation_type in ['clustering', 'idea_generation', 'content_generation']: return 'gpt-4o-mini' elif operation_type == 'image_generation': return 'hidream-full' # ... existing logic for paid accounts ``` **Pros:** Centralized plan-based control, scalable **Cons:** Requires migration + AI operation logic changes #### **Step 5: Frontend Restrictions** ```typescript // In frontend, check plan limits if (user.account.plan.slug === 'free') { // Hide model selector (force defaults) // Show "Upgrade for more models" message // Disable credit topup // Disable Ahrefs research } ``` ### **✅ Pros: Individual Free Accounts** 1. **Full User Experience** - Users get their own workspace, test all features 2. **Data Isolation** - Private data, no cross-contamination 3. **Smooth Upgrade Path** - Existing account → upgrade plan → keep data 4. **Proper Multi-Tenancy** - Each account is isolated, secure 5. **Credit Tracking** - Accurate per-user usage analytics 6. **Marketing Value** - "100 Free Credits" sounds generous ### **❌ Cons: Individual Free Accounts** 1. **Database Growth** - Each user = new Account + User + potential Sites/Keywords 2. **Abuse Potential** - Users can create multiple emails for free credits 3. **Complex Enforcement** - Need to enforce model restrictions per account 4. **Storage Costs** - Each account stores independent data 5. **Migration Complexity** - If user upgrades, need to handle plan transition ### **Effort Estimate: Individual Free Accounts** - **Minimal Approach** (Option A): **1 hour** - Create free plan via admin - Set default models globally - Update frontend to hide topup for free users - **Recommended Approach** (Option B): **4-6 hours** - Create free plan via admin - Update registration to create AISettings per free account - Update AI operation logic to read account-specific models - Frontend: Hide model selector for free users - Testing across all AI operations - **Enterprise Approach** (Option C): **1-2 days** - Migration: Add allowed_models fields to Plan - Update registration flow - Refactor AI operation logic (all modules) - Admin UI for plan model management - Comprehensive testing --- ## 🎭 Option 2: Shared Demo Account (Multi-User) ### **Concept** One demo account shared by multiple users: - Users provide email → get "demo access" token - Limited operations pool (50-100 per user, tracked separately) - Shared data (users see what others created) - Pre-configured cheaper AI models - No upgrade path (must create new account) ### **Implementation Plan** #### **Step 1: Create Demo Account** ```sql -- Create demo plan (internal) INSERT INTO igny8_plans ( name, slug, price, billing_cycle, is_internal, is_active, max_sites, max_users, max_keywords, included_credits, allow_credit_topup ) VALUES ( 'Demo Access', 'demo', 0.00, 'monthly', true, true, 1, -- 1 demo site 999, -- Unlimited demo users 50, -- Limited keywords 10000, -- Large shared pool false ); -- Create demo account INSERT INTO igny8_tenants ( name, slug, owner_id, plan_id, credits, status ) VALUES ( 'IGNY8 Demo Workspace', 'igny8-demo', 1, -- owner = admin (SELECT id FROM igny8_plans WHERE slug='demo'), 10000, 'active' ); -- Create demo site INSERT INTO igny8_sites ( name, url, account_id, is_active ) VALUES ( 'Demo Content Site', 'https://demo.example.com', (SELECT id FROM igny8_tenants WHERE slug='igny8-demo'), true ); ``` #### **Step 2: Create DemoUserAccess Model** ```python # In auth/models.py class DemoUserAccess(models.Model): """Track individual demo user access and limits""" email = models.EmailField(unique=True, db_index=True) demo_account = models.ForeignKey( 'Account', on_delete=models.CASCADE, related_name='demo_users' ) access_token = models.CharField(max_length=255, unique=True) operations_used = models.IntegerField(default=0) operations_limit = models.IntegerField(default=50) created_at = models.DateTimeField(auto_now_add=True) last_accessed = models.DateTimeField(auto_now=True) is_active = models.BooleanField(default=True) class Meta: db_table = 'igny8_demo_user_access' indexes = [ models.Index(fields=['email', 'is_active']), models.Index(fields=['access_token']), ] def __str__(self): return f"Demo: {self.email} ({self.operations_used}/{self.operations_limit})" def has_operations_remaining(self): return self.operations_used < self.operations_limit ``` #### **Step 3: Migration** ```python # migrations/0014_demo_user_access.py from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ('igny8_core_auth', '0013_add_plan_is_internal'), ] operations = [ migrations.CreateModel( name='DemoUserAccess', fields=[ ('id', models.AutoField(primary_key=True)), ('email', models.EmailField(unique=True, db_index=True)), ('demo_account', models.ForeignKey( on_delete=models.CASCADE, to='igny8_core_auth.Account', related_name='demo_users' )), ('access_token', models.CharField(max_length=255, unique=True)), ('operations_used', models.IntegerField(default=0)), ('operations_limit', models.IntegerField(default=50)), ('created_at', models.DateTimeField(auto_now_add=True)), ('last_accessed', models.DateTimeField(auto_now=True)), ('is_active', models.BooleanField(default=True)), ], options={'db_table': 'igny8_demo_user_access'}, ), ] ``` #### **Step 4: Demo Access Endpoint** ```python # In auth/views.py AuthViewSet @action(detail=False, methods=['post'], permission_classes=[]) def request_demo_access(self, request): """Request demo account access with email only""" email = request.data.get('email') if not email: return error_response( error='Email is required', status_code=status.HTTP_400_BAD_REQUEST, request=request ) # Validate email format from django.core.validators import validate_email from django.core.exceptions import ValidationError try: validate_email(email) except ValidationError: return error_response( error='Invalid email format', status_code=status.HTTP_400_BAD_REQUEST, request=request ) # Get demo account try: demo_account = Account.objects.get(slug='igny8-demo', status='active') except Account.DoesNotExist: return error_response( error='Demo account not configured', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, request=request ) # Get or create demo user access from .models import DemoUserAccess import secrets demo_user, created = DemoUserAccess.objects.get_or_create( email=email, demo_account=demo_account, defaults={ 'access_token': secrets.token_urlsafe(32), 'operations_limit': 50, } ) if not demo_user.is_active: return error_response( error='Demo access suspended. Please contact support.', status_code=status.HTTP_403_FORBIDDEN, request=request ) if not demo_user.has_operations_remaining(): return error_response( error='Demo operation limit reached. Please sign up for a full account.', status_code=status.HTTP_429_TOO_MANY_REQUESTS, request=request ) # Generate temporary JWT for demo account # (Custom token that includes demo_user_id) access_token = generate_demo_access_token(demo_account, demo_user) return success_response( data={ 'access_token': access_token, 'demo_user': { 'email': demo_user.email, 'operations_remaining': demo_user.operations_limit - demo_user.operations_used, 'operations_limit': demo_user.operations_limit, }, 'account': { 'id': demo_account.id, 'name': demo_account.name, 'is_demo': True, } }, message='Demo access granted' if created else 'Welcome back to demo', request=request ) ``` #### **Step 5: Custom JWT with Demo Context** ```python # In auth/utils.py def generate_demo_access_token(account, demo_user): """Generate JWT for demo access with demo_user context""" import jwt from datetime import datetime, timedelta from django.conf import settings expiry = datetime.utcnow() + timedelta(hours=24) # 24-hour demo session payload = { 'account_id': account.id, 'account_slug': account.slug, 'demo_user_id': demo_user.id, 'demo_user_email': demo_user.email, 'is_demo': True, 'exp': expiry, 'iat': datetime.utcnow(), } return jwt.encode(payload, settings.SECRET_KEY, algorithm='HS256') ``` #### **Step 6: Demo Operation Tracking Middleware** ```python # In middleware/demo_tracking.py class DemoOperationTrackingMiddleware: """Track demo user operations and enforce limits""" def __init__(self, get_response): self.get_response = get_response def __call__(self, request): # Check if demo user if hasattr(request, 'demo_user_id'): from igny8_core.auth.models import DemoUserAccess demo_user = DemoUserAccess.objects.select_for_update().get( id=request.demo_user_id ) # Check limit before processing if not demo_user.has_operations_remaining(): return JsonResponse({ 'success': False, 'error': 'Demo operation limit reached. Please sign up for a full account.', 'upgrade_url': '/pricing' }, status=429) # Store demo_user in request for operation tracking request.demo_user = demo_user response = self.get_response(request) return response # Add to settings.py MIDDLEWARE ``` #### **Step 7: Update AI Operation Logic** ```python # In ai/services.py or wherever AI operations are tracked def log_ai_operation(account, operation_type, credits_used, **kwargs): """Log AI operation and increment demo counter if demo user""" from igny8_core.business.billing.models import CreditUsageLog from django.db import transaction with transaction.atomic(): # Create credit usage log CreditUsageLog.objects.create( account=account, operation_type=operation_type, credits_used=credits_used, **kwargs ) # Deduct credits from account account.credits -= credits_used account.save() # If demo user, increment their personal counter from threading import local _request = getattr(local(), 'request', None) if _request and hasattr(_request, 'demo_user'): demo_user = _request.demo_user demo_user.operations_used += 1 demo_user.save() ``` #### **Step 8: Frontend Demo Flow** ```typescript // New demo signup flow async function requestDemoAccess(email: string) { const response = await api.post('/v1/auth/request-demo-access/', { email }); if (response.success) { // Store demo token localStorage.setItem('demo_token', response.data.access_token); localStorage.setItem('is_demo', 'true'); // Show demo banner showDemoBanner({ operationsRemaining: response.data.demo_user.operations_remaining, operationsLimit: response.data.demo_user.operations_limit, }); // Redirect to demo workspace router.push('/dashboard'); } } // Demo banner component

🎭 You're in Demo Mode - {operationsRemaining} operations remaining

// Disable certain features in demo mode if (isDemo) { disableFeatures(['integrations', 'automation', 'wordpress_sync']); showSharedDataWarning(); } ``` ### **✅ Pros: Shared Demo Account** 1. **Zero Database Growth** - One account, minimal new records 2. **Instant Access** - No account creation, just email → token 3. **Showcase Content** - Users see real AI-generated examples from others 4. **Anti-Abuse** - Email-based tracking, hard limits per email 5. **Conversion Pressure** - "See others creating, sign up for your own workspace" 6. **Cost Efficient** - Shared credit pool, bulk tracking ### **❌ Cons: Shared Demo Account** 1. **No Data Privacy** - All users see shared workspace (could be feature or bug) 2. **Complex Access Control** - Need custom JWT + middleware + tracking 3. **No Upgrade Path** - Demo token ≠ real account, must register separately 4. **Shared Credit Pool** - If pool exhausted, demo is down for everyone 5. **Feature Limitations** - Can't show integrations, automation, publishing 6. **User Confusion** - "Why do I see others' content?" + "Lost my demo data!" 7. **Backend Complexity** - New model, middleware, JWT type, operation tracking ### **Effort Estimate: Shared Demo Account** **Full Implementation**: **2-3 days** - Create demo plan + account + site (1 hour) - Create DemoUserAccess model + migration (2 hours) - Build request_demo_access endpoint (2 hours) - Custom JWT generation with demo context (2 hours) - Middleware for demo tracking + limits (3 hours) - Update AI operation logging (2 hours) - Frontend: Demo flow + banner + restrictions (4 hours) - Admin: Dashboard to manage demo users (2 hours) - Testing: Edge cases, limits, shared data (4 hours) --- ## 🎯 Recommendation ### **🏆 Winner: Option 1 - Individual Free Accounts (Option B)** **Rationale:** 1. **Simpler Architecture** - Leverages existing multi-tenancy, no custom JWT/middleware 2. **Better UX** - Private workspace, smooth upgrade path, feels like real product 3. **Faster Implementation** - 4-6 hours vs 2-3 days 4. **Lower Risk** - No shared data confusion, no new access control layer 5. **Marketing Win** - "100 Free Credits" > "Demo Access with Shared Data" 6. **Scalable** - If abuse becomes issue, add email verification or captcha **Implementation Checklist:** ```markdown - [ ] Create 'free' plan via Django Admin - [ ] Set: included_credits=100, max_sites=1, max_keywords=100 - [ ] Set: is_internal=true, allow_credit_topup=false - [ ] Verify AI Model Configs exist - [ ] GPT-4o Mini (text, cheap) - [ ] Hidream Full (image, cheap) - [ ] Update RegisterSerializer (auth/serializers.py) - [ ] After account creation for trial status: - [ ] Create AISettings for openai (text) → gpt-4o-mini - [ ] Create AISettings for runware (images) → hidream-full - [ ] Update Frontend - [ ] Hide model selector for free plan - [ ] Disable credit topup for free plan - [ ] Show "Upgrade for more models" CTA - [ ] Testing - [ ] Register new free account - [ ] Run text AI operation → verify gpt-4o-mini used - [ ] Run image AI operation → verify hidream-full used - [ ] Verify 100 credits allocated - [ ] Verify upgrade flow works ``` --- ## 🔮 Future Enhancements (Optional) ### For Option 1 (Individual Free Accounts): 1. **Email Verification** - Require verified email to prevent abuse 2. **Captcha** - Add reCAPTCHA on free signups 3. **Usage Analytics** - Track free-to-paid conversion rates 4. **Referral Credits** - Give 50 bonus credits for referrals 5. **Time-Limited Trial** - 30-day access instead of credit-limited ### For Option 2 (Shared Demo - If Pursued): 1. **Demo Content Curation** - Pre-seed with high-quality examples 2. **Demo Reset** - Daily reset to clean state 3. **Anonymous Mode** - Show "User A, User B" instead of emails 4. **Live Activity Feed** - "User just generated an article about X" 5. **Demo Leaderboard** - Gamify the experience --- ## 📚 Reference Files **Models:** - `/backend/igny8_core/auth/models.py` - Account, Plan, User - `/backend/igny8_core/business/billing/models.py` - AIModelConfig, CreditTransaction, CreditUsageLog - `/backend/igny8_core/modules/system/settings_models.py` - AISettings **Registration:** - `/backend/igny8_core/auth/serializers.py` - RegisterSerializer - `/backend/igny8_core/auth/views.py` - AuthViewSet.register() **AI Operations:** - Check modules: clustering, ideas, content, images for credit deduction logic --- ## ✅ Decision **Recommended:** Proceed with **Option 1 - Individual Free Accounts (Option B)** **Estimated Time:** 4-6 hours **Risk Level:** Low **User Experience:** Excellent Consider **Option 2** only if: - Need to showcase "collaborative" aspect - Want zero database growth (high traffic expected) - Marketing wants "see what others create" feature