# Credit System **Last Verified:** January 5, 2026 **Status:** ✅ Simplified (v1.5.0) --- ## Overview IGNY8 uses a unified credit system where all AI operations consume credits from a single balance. Plan limits are simplified to 4 hard/monthly limits only. --- ## Credit Flow (Verified Architecture) ``` ┌─────────────────────────────────────────────────────────────────┐ │ CREDIT FLOW │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Plan.included_credits = Monthly allocation (e.g., 10,000) │ │ ↓ (Added on subscription renewal/approval) │ │ Account.credits = Current balance (real-time, decremented) │ │ ↓ (Decremented on each AI operation) │ │ CreditTransaction = Log of all credit changes │ │ CreditUsageLog = Detailed operation tracking │ │ │ │ THESE ARE NOT PARALLEL - They serve different purposes: │ │ • Plan.included_credits = "How many credits per month" │ │ • Account.credits = "How many credits you have RIGHT NOW" │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **Where credits are added to Account.credits:** 1. `billing/views.py` - When manual payment is approved 2. `payment_service.py` - When credit package purchased 3. `credit_service.py` - Generic `add_credits()` method --- ## Simplified Limits (v1.5.0) ### Hard Limits (Never Reset) | Limit | Plan Field | Account Field | Description | |-------|------------|---------------|-------------| | Sites | `max_sites` | (count of Site objects) | Maximum sites per account | | Users | `max_users` | (count of User objects) | Maximum team members | | Keywords | `max_keywords` | (count of Keyword objects) | Total keywords allowed | ### Monthly Limits (Reset on Billing Cycle) | Limit | Plan Field | Account Field | Description | |-------|------------|---------------|-------------| | Ahrefs Queries | `max_ahrefs_queries` | `usage_ahrefs_queries` | Live Ahrefs API queries per month | ### Removed Limits (Now Credit-Based) The following limits were removed in v1.5.0 - credits handle these: - ~~max_clusters~~ → Credits - ~~max_content_ideas~~ → Credits - ~~max_content_words~~ → Credits - ~~max_images_basic~~ → Credits - ~~max_images_premium~~ → Credits - ~~max_image_prompts~~ → Credits --- ## Plan Tiers | Plan | Credits/Month | Sites | Users | Keywords | Ahrefs Queries | |------|---------------|-------|-------|----------|----------------| | Free | 500 | 1 | 1 | 100 | 0 | | Starter | 5,000 | 3 | 2 | 500 | 50 | | Growth | 15,000 | 10 | 5 | 2,000 | 200 | | Scale | 50,000 | Unlimited | 10 | 10,000 | 500 | --- ## Credit Operations ### Token-Based Operations (Text AI) Credits calculated from actual token usage: - `credits = ceil(total_tokens / tokens_per_credit)` - `tokens_per_credit` defined per model in `AIModelConfig` | Operation | Model Example | tokens_per_credit | |-----------|---------------|-------------------| | Keyword Clustering | gpt-4o-mini | 10,000 | | Idea Generation | gpt-4o-mini | 10,000 | | Content Generation | gpt-4o | 1,000 | | Content Optimization | gpt-4o-mini | 10,000 | ### Fixed-Cost Operations (Image AI) Credits per image based on quality tier: | Quality Tier | Model Example | Credits/Image | |--------------|---------------|---------------| | Basic | runware:97@1 | 1 | | Quality | dall-e-3 | 5 | | Premium | google:4@2 | 15 | ### Free Operations | Operation | Cost | |-----------|------| | Add keyword (manual) | 0 | | Create content task | 0 | | Edit content | 0 | | Publish to WordPress | 0 | | Sync from WordPress | 0 | --- ## Database Models ### Account (Credit Balance) ```python class Account(models.Model): credits = models.IntegerField(default=0) # Current balance usage_ahrefs_queries = models.IntegerField(default=0) # Monthly Ahrefs usage ``` ### Plan (Allocations) ```python class Plan(models.Model): included_credits = models.IntegerField(default=0) # Monthly allocation max_sites = models.IntegerField(default=1) max_users = models.IntegerField(default=1) max_keywords = models.IntegerField(default=100) max_ahrefs_queries = models.IntegerField(default=0) # Monthly Ahrefs limit ``` ### CreditTransaction (Ledger) ```python class CreditTransaction(models.Model): account = models.ForeignKey(Account) transaction_type = models.CharField() # purchase/subscription/refund/deduction amount = models.DecimalField() # Positive (add) or negative (deduct) balance_after = models.DecimalField() description = models.CharField() created_at = models.DateTimeField() ``` ### CreditUsageLog (Analytics) ```python class CreditUsageLog(models.Model): account = models.ForeignKey(Account) operation_type = models.CharField() # clustering/content_generation/image_generation credits_used = models.DecimalField() model_used = models.CharField() tokens_input = models.IntegerField() tokens_output = models.IntegerField() created_at = models.DateTimeField() ``` --- ## Business Logic ### CreditService Location: `backend/igny8_core/business/billing/services/credit_service.py` **Key Methods:** ```python class CreditService: @staticmethod def check_credits(account, required_credits): """Check if sufficient credits available, raises InsufficientCreditsError if not""" @staticmethod def deduct_credits_for_operation(account, operation_type, model, tokens_in, tokens_out, metadata=None): """Deduct credits and log usage after AI operation""" @staticmethod def add_credits(account, amount, transaction_type, description): """Add credits (admin/purchase/subscription)""" @staticmethod def calculate_credits_from_tokens(operation_type, tokens_in, tokens_out, model=None): """Calculate credits based on token usage and model""" ``` ### LimitService Location: `backend/igny8_core/business/billing/services/limit_service.py` **Key Methods:** ```python class LimitService: HARD_LIMIT_MAPPINGS = { 'sites': {...}, 'users': {...}, 'keywords': {...}, } MONTHLY_LIMIT_MAPPINGS = { 'ahrefs_queries': {...}, } @classmethod def check_hard_limit(cls, account, limit_name, additional_count=1): """Check if adding items would exceed hard limit""" @classmethod def check_monthly_limit(cls, account, limit_name, additional_count=1): """Check if operation would exceed monthly limit""" @classmethod def increment_monthly_usage(cls, account, limit_name, count=1): """Increment monthly usage counter""" ``` ### Usage in AI Operations ```python # In content generation service def generate_content(task, user): account = task.site.account # 1. Pre-check credits (estimated) estimated_credits = 50 # Estimate for content generation CreditService.check_credits(account, estimated_credits) # 2. Execute AI function content, usage = ai_engine.generate_content(task) # 3. Deduct actual credits based on token usage CreditService.deduct_credits_for_operation( account=account, operation_type='content_generation', model=usage.model, tokens_in=usage.input_tokens, tokens_out=usage.output_tokens, metadata={'content_id': content.id} ) return content ``` --- ## API Responses ### Successful Operation ```json { "success": true, "data": { ... }, "credits_used": 15, "balance": 9985 } ``` ### Insufficient Credits ```json HTTP 402 Payment Required { "success": false, "error": "Insufficient credits", "code": "INSUFFICIENT_CREDITS", "required": 50, "available": 25 } ``` ### Limit Exceeded ```json HTTP 402 Payment Required { "success": false, "error": "Keyword limit reached", "code": "HARD_LIMIT_EXCEEDED", "limit": "keywords", "current": 500, "max": 500 } ``` --- ## Frontend Handling ### Credit Balance Display - Header shows current credit balance - Updates after each operation - Warning at low balance (< 10%) ### Pre-Operation Check ```typescript import { checkCreditsBeforeOperation } from '@/utils/creditCheck'; import { useInsufficientCreditsModal } from '@/components/billing/InsufficientCreditsModal'; function ContentGenerator() { const { showModal } = useInsufficientCreditsModal(); const handleGenerate = async () => { // Check credits before operation const check = await checkCreditsBeforeOperation(50); // estimated cost if (!check.hasEnoughCredits) { showModal({ requiredCredits: check.requiredCredits, availableCredits: check.availableCredits, }); return; } // Proceed with generation await generateContent(); }; } ``` --- ## API Endpoints ### Credit Balance ``` GET /api/v1/billing/balance/ ``` Response: ```json { "credits": 9500, "plan_credits_per_month": 10000, "credits_used_this_month": 500, "credits_remaining": 9500 } ``` ### Usage Limits ``` GET /api/v1/billing/usage/limits/ ``` Response: ```json { "limits": { "sites": { "current": 2, "limit": 5, "type": "hard" }, "users": { "current": 2, "limit": 3, "type": "hard" }, "keywords": { "current": 847, "limit": 1000, "type": "hard" }, "ahrefs_queries": { "current": 23, "limit": 50, "type": "monthly" } }, "days_until_reset": 18 } ``` ### Usage Analytics ``` GET /api/v1/account/usage/analytics/?days=30 ``` Response: ```json { "period_days": 30, "start_date": "2025-12-06", "end_date": "2026-01-05", "current_balance": 9500, "total_usage": 500, "total_purchases": 0, "usage_by_type": [ { "transaction_type": "content_generation", "total": -350, "count": 15 }, { "transaction_type": "image_generation", "total": -100, "count": 20 }, { "transaction_type": "clustering", "total": -50, "count": 10 } ], "daily_usage": [ { "date": "2026-01-05", "usage": 25, "purchases": 0, "net": -25 } ] } ``` --- ## Credit Allocation Credits are added to `Account.credits` when: 1. **Subscription Renewal** - `Plan.included_credits` added monthly 2. **Payment Approval** - Manual payments approved by admin 3. **Credit Purchase** - Credit packages bought by user 4. **Admin Adjustment** - Manual credit grants/adjustments ### Monthly Reset Monthly limits (Ahrefs queries) reset on billing cycle: ```python # In Account model def reset_monthly_usage(self): """Reset monthly usage counters (called on billing cycle renewal)""" self.usage_ahrefs_queries = 0 self.save(update_fields=['usage_ahrefs_queries']) ``` --- ## Admin Operations ### Manual Credit Adjustment Via Django Admin or API: ```python from igny8_core.business.billing.services.credit_service import CreditService # Add credits CreditService.add_credits( account=account, amount=1000, transaction_type='adjustment', description='Customer support adjustment' ) ``` ### Usage Audit All credit changes logged in `CreditTransaction` with: - Timestamp - Transaction type - Amount (positive or negative) - Balance after transaction - Description All AI operations logged in `CreditUsageLog` with: - Operation type - Credits used - Model used - Token counts - Related object metadata