20 KiB
Item 2: Credits, Billing, Pricing Logic, and Usage Limits
Priority: Critical
Target: Production Launch
Last Updated: December 11, 2025
Overview
Define and implement a comprehensive credit cost system, plan-based usage limits, and billing logic for all AI operations. This includes setting credit costs per function, establishing plan tiers with limits, and implementing enforcement mechanisms across backend and frontend.
Current Implementation Analysis
Credit System Architecture
Location: backend/igny8_core/business/billing/
Credit Models
| Model | Purpose | Key Fields |
|---|---|---|
| CreditTransaction | Tracks all credit additions/deductions | transaction_type, amount, balance_after, description |
| CreditUsageLog | Detailed log per AI operation | operation_type, credits_used, cost_usd, model_used, tokens_input, tokens_output |
| CreditCostConfig | Admin-configurable credit costs | operation_type, credits_cost, unit, display_name |
Credit Transaction Types:
purchase- Credit purchasesubscription- Monthly subscription renewalrefund- Credit refunddeduction- Usage deduction (AI operations)adjustment- Manual admin adjustment
Credit Service
Location: backend/igny8_core/business/billing/services/credit_service.py
Methods:
get_credit_cost(operation_type, amount)- Calculate cost for operationcheck_credits(account, operation_type, amount)- Validate sufficient creditsdeduct_credits(account, amount, operation_type, ...)- Deduct and logdeduct_credits_for_operation(...)- Convenience method with auto-calculation
Logic:
- Checks database
CreditCostConfigfirst - Falls back to hardcoded
CREDIT_COSTSconstants - Applies unit-based calculation (per 100 words, per image, etc.)
- Validates sufficient balance before deduction
- Creates both
CreditTransactionandCreditUsageLogrecords
Current Credit Costs
Location: backend/igny8_core/business/billing/constants.py
| Operation | Current Cost | Unit | Notes |
|---|---|---|---|
clustering |
10 credits | per request | Clusters all submitted keywords |
idea_generation |
15 credits | per request | Ideas for one cluster |
content_generation |
1 credit | per 100 words | Word-count based |
image_prompt_extraction |
2 credits | per content | Extract prompts from content |
image_generation |
5 credits | per image | Generate single image |
linking |
8 credits | per content | Internal linking (NEW) |
optimization |
1 credit | per 200 words | Content optimization (NEW) |
site_structure_generation |
50 credits | per site | Site blueprint (Phase 7) |
site_page_generation |
20 credits | per page | Page generation (Phase 7) |
Legacy Aliases:
ideas→idea_generationcontent→ 3 credits fixed (legacy)images→image_generationreparse→ 1 credit
Issues with Current Costs:
- Not optimized for profitability - Costs may not reflect actual AI provider costs
- Arbitrary values - No clear formula based on model costs, processing time, or value
- Inconsistent granularity - Some per-request, some per-word, some per-item
- No differentiation by quality - Same cost regardless of model quality (GPT-4 vs GPT-3.5)
Plan Model and Limits
Location: backend/igny8_core/auth/models.py - Plan model
Current Plan Structure
| Field | Purpose | Current State |
|---|---|---|
name, slug |
Plan identification | ✅ Implemented |
price, billing_cycle |
Pricing | ✅ Monthly/Annual support |
included_credits |
Monthly credit allocation | ✅ Implemented |
extra_credit_price |
Per-credit overage cost | ✅ Default $0.01 |
allow_credit_topup |
Can buy more credits | ✅ Boolean flag |
auto_credit_topup_threshold |
Auto-buy trigger | ✅ Optional |
auto_credit_topup_amount |
Auto-buy amount | ✅ Optional |
max_users |
Users per account | ✅ Implemented |
max_sites |
Sites per account | ✅ Implemented |
max_industries |
Industries/sectors limit | ✅ Optional |
max_author_profiles |
Writing styles limit | ✅ Default 5 |
What's MISSING:
- ❌ Max keywords limit
- ❌ Max clusters limit
- ❌ Max ideas limit
- ❌ Max content pieces limit
- ❌ Max images limit
- ❌ Max tasks in queue limit
- ❌ Daily/monthly usage caps (beyond credits)
- ❌ Per-user vs per-account limits distinction
Account Credit Balance
Location: backend/igny8_core/auth/models.py - Account model
Field: credits (IntegerField with MinValueValidator(0))
Current Behavior:
- Credits deducted on AI operation completion
- Credit balance checked before operation starts
InsufficientCreditsErrorraised if balance < required
No Implementation For:
- Credit expiration dates
- Credit rollover rules (monthly vs annual)
- Negative balance prevention (hard stop vs warning)
- Credit reserve for pending operations
Pricing Plan Requirements
Recommended Plan Tiers
Based on industry standards and target market:
| Plan | Monthly Price | Annual Price | Included Credits | Target User |
|---|---|---|---|---|
| Free | $0 | $0 | 50 | Trial users, hobbyists |
| Starter | $29 | $299 (15% off) | 500 | Solo creators, small blogs |
| Growth | $99 | $1,019 (15% off) | 2,000 | Growing sites, agencies |
| Pro | $299 | $3,077 (15% off) | 7,500 | Power users, large agencies |
| Enterprise | Custom | Custom | Custom | Enterprise clients |
Free Plan Considerations:
- Should be marked
is_internal=Trueto hide from public pricing - Limits should be strict enough to encourage upgrade
- Should not include advanced features (automation, API access)
Usage Limits Per Plan
Proposed Limits (to be finalized):
| Limit Type | Free | Starter | Growth | Pro | Enterprise |
|---|---|---|---|---|---|
| Monthly Credits | 50 | 500 | 2,000 | 7,500 | Custom |
| Max Users | 1 | 2 | 5 | 15 | Unlimited |
| Max Sites | 1 | 3 | 10 | 50 | Unlimited |
| Max Keywords (saved) | 100 | 1,000 | 5,000 | 25,000 | Unlimited |
| Max Clusters | 20 | 100 | 500 | 2,500 | Unlimited |
| Max Ideas (saved) | 50 | 500 | 2,500 | 12,500 | Unlimited |
| Max Content Pieces | 25 | 250 | 1,250 | 6,250 | Unlimited |
| Max Images | 25 | 250 | 1,250 | 6,250 | Unlimited |
| Max Queue Size | 5 | 20 | 50 | 200 | Unlimited |
| Automation Enabled | ❌ | ❌ | ✅ | ✅ | ✅ |
| API Access | ❌ | ❌ | ✅ | ✅ | ✅ |
| Priority Support | ❌ | ❌ | ❌ | ✅ | ✅ |
Notes:
- "Unlimited" means no hard limit, but still subject to fair use policy
- Limits apply per account (across all sites in account)
- Deleted items don't count toward limits (soft-delete system)
Credit Cost Optimization Strategy
Goal: Define credit costs that:
- Cover AI provider costs + margin
- Are competitive with market rates
- Encourage usage without abuse
- Scale predictably with usage
Recommended Credit Cost Revisions
Analysis Required:
- Calculate actual AI provider costs per operation (OpenAI, Runware, etc.)
- Add 30-50% margin for infrastructure, support, and profit
- Compare with competitor pricing (Jasper, Copy.ai, Writesonic)
- Test with sample use cases to ensure plan value
Proposed Adjustments (pending analysis):
| Operation | Current | Proposed | Reasoning |
|---|---|---|---|
| Clustering | 10 | 8 | Lower barrier for discovery phase |
| Idea Generation | 15 | 12 | Encourage ideation before writing |
| Content (100 words) | 1 | 1.5 | Reflect actual GPT-4 costs |
| Image Prompts | 2 | 3 | More complex extraction logic |
| Image Generation | 5 | 6 | Runware/DALL-E costs increasing |
| Optimization | 1 per 200 words | 0.5 per 100 words | Encourage optimization usage |
Variable Costs by Model Quality:
- Option: Charge more for GPT-4 vs GPT-3.5, DALL-E 3 vs DALL-E 2
- Implementation: Add
model_tiermultiplier inget_credit_cost()
Required Implementation
A. Expand Plan Model with Usage Limits
File: backend/igny8_core/auth/models.py - Plan model
Add Fields:
# Content Creation Limits (NULL = unlimited)
max_keywords = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum keywords saved per account"
)
max_clusters = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum clusters per account"
)
max_ideas = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum content ideas saved per account"
)
max_content = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum content pieces per account"
)
max_images = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum images per account"
)
# Queue and Rate Limits
max_queue_size = models.IntegerField(
default=10,
validators=[MinValueValidator(1)],
help_text="Maximum concurrent items in queue"
)
max_daily_ai_requests = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum AI requests per day (prevents abuse)"
)
max_monthly_content_generated = models.IntegerField(
null=True, blank=True,
validators=[MinValueValidator(1)],
help_text="Maximum content pieces generated per month"
)
# Feature Access Flags
allow_automation = models.BooleanField(
default=False,
help_text="Enable automation wizard"
)
allow_api_access = models.BooleanField(
default=False,
help_text="Enable API access"
)
allow_bulk_operations = models.BooleanField(
default=True,
help_text="Enable bulk actions (delete, export, etc.)"
)
Migration: Create Django migration to add these fields with default NULL values
B. Create Limit Enforcement Service
File: backend/igny8_core/business/billing/services/limit_service.py (NEW)
Service Class: LimitService
Methods to Implement:
| Method | Purpose | Returns |
|---|---|---|
check_keyword_limit(account) |
Check if can add more keywords | bool or raises LimitExceededError |
check_cluster_limit(account) |
Check if can add more clusters | bool or raises LimitExceededError |
check_idea_limit(account) |
Check if can add more ideas | bool or raises LimitExceededError |
check_content_limit(account) |
Check if can add more content | bool or raises LimitExceededError |
check_image_limit(account) |
Check if can add more images | bool or raises LimitExceededError |
check_queue_limit(account) |
Check queue capacity | bool or raises LimitExceededError |
check_daily_request_limit(account) |
Check daily AI request quota | bool or raises LimitExceededError |
get_usage_stats(account) |
Get current usage counts | dict with all counters |
get_limit_stats(account) |
Get limits and remaining capacity | dict with limits |
Implementation Logic:
def check_keyword_limit(account):
plan = account.plan
if plan.max_keywords is None:
return True # Unlimited
current_count = Keywords.objects.filter(
account=account,
deleted_at__isnull=True # Exclude soft-deleted
).count()
if current_count >= plan.max_keywords:
raise LimitExceededError(
f"Keyword limit reached ({plan.max_keywords}). Upgrade your plan."
)
return True
Exception: LimitExceededError (inherit from BillingException)
C. Integrate Limit Checks in API Views
Files to Update:
backend/igny8_core/modules/planner/views.py- KeywordsViewSetbackend/igny8_core/modules/planner/views.py- ClustersViewSetbackend/igny8_core/modules/planner/views.py- ContentIdeasViewSetbackend/igny8_core/modules/writer/views.py- TasksViewSetbackend/igny8_core/modules/writer/views.py- ContentViewSet
Integration Points:
| ViewSet | Action | Check to Add |
|---|---|---|
| KeywordsViewSet | create() |
LimitService.check_keyword_limit(account) |
| KeywordsViewSet | bulk_create() |
Check limit with proposed count |
| ClustersViewSet | create() |
LimitService.check_cluster_limit(account) |
| ContentIdeasViewSet | create() |
LimitService.check_idea_limit(account) |
| TasksViewSet | create() |
LimitService.check_content_limit(account) + check_queue_limit() |
| ContentViewSet | create() |
LimitService.check_content_limit(account) |
Example Integration:
def create(self, request, *args, **kwargs):
account = request.user.account
# Check limit before creating
try:
LimitService.check_keyword_limit(account)
except LimitExceededError as e:
return Response({
'error': str(e),
'error_code': 'LIMIT_EXCEEDED',
'upgrade_url': '/pricing'
}, status=403)
# Proceed with creation
return super().create(request, *args, **kwargs)
D. Add Usage Tracking and Counter Cache
Optimization: Instead of counting records on every request, cache counts
Implementation Options:
Option 1: Add Counter Fields to Account Model
# Add to Account model
keyword_count = models.IntegerField(default=0)
cluster_count = models.IntegerField(default=0)
idea_count = models.IntegerField(default=0)
content_count = models.IntegerField(default=0)
image_count = models.IntegerField(default=0)
Update counters in signals:
post_savesignal: increment counterpost_deletesignal: decrement counter- Periodic reconciliation task to fix drift
Option 2: Cache Usage Stats (Recommended)
Use Django cache with 5-minute TTL:
def get_cached_usage_stats(account):
cache_key = f'usage_stats_{account.id}'
stats = cache.get(cache_key)
if stats is None:
stats = {
'keywords': Keywords.objects.filter(account=account, deleted_at__isnull=True).count(),
'clusters': Clusters.objects.filter(account=account, deleted_at__isnull=True).count(),
# ... etc
}
cache.set(cache_key, stats, 300) # 5 minutes
return stats
Invalidate cache on:
- Create operations
- Delete operations
- Soft-delete operations
E. Frontend Limit Display
1. Usage Dashboard Widget
Location: frontend/src/components/dashboard/UsageLimitsWidget.tsx (NEW)
Display:
- Current usage vs limit for each resource
- Progress bars with color coding:
- Green: < 70% used
- Yellow: 70-90% used
- Red: > 90% used
- "Upgrade Plan" button when approaching limits
Example UI:
Usage & Limits
━━━━━━━━━━━━━━━━━━━━━━━━━━━
Keywords: 750 / 1,000 ████████░░ 75%
Clusters: 45 / 100 ████░░░░░░ 45%
Ideas: 380 / 500 ███████░░░ 76%
Content: 120 / 250 ████░░░░░░ 48%
[Upgrade Plan]
2. Inline Warnings
Show warnings when approaching limits:
- At 80%: Yellow badge "Approaching limit"
- At 90%: Orange warning "Near limit - Upgrade recommended"
- At 100%: Red error "Limit reached - Upgrade required"
Display in:
- Header metrics
- Page headers
- Before bulk operations
- In forms (disable submit if limit reached)
3. Create/Import Dialogs
Add limit check before showing form:
const handleCreateKeyword = () => {
const stats = usageStats; // from API
const limit = account.plan.max_keywords;
if (limit && stats.keywords >= limit) {
toast.error('Keyword limit reached. Upgrade your plan.');
navigate('/settings/billing');
return;
}
setShowCreateModal(true);
};
4. Upgrade Prompts
When limit error occurs:
- Show modal with:
- Current plan
- Current limit
- Recommended plan
- Benefits of upgrading
- "Upgrade Now" CTA
F. Credit Cost Configuration UI (Admin)
Location: Django Admin or custom Admin Panel page
Feature: Allow superusers to edit credit costs without code changes
Admin Interface:
- List all operations with current costs
- Edit cost, unit, and display name
- Track change history (previous_cost field)
- Enable/disable operations
- Preview impact on sample use cases
Models Used:
CreditCostConfig- Admin-editable costs- Falls back to
CREDIT_COSTSconstants if not configured
Testing Requirements
Limit Enforcement Tests
| Test Case | Expected Result |
|---|---|
| Create keyword at limit | Error: "Keyword limit reached" |
| Create keyword below limit | Success |
| Create 10 keywords via bulk import at limit | Error with count blocked |
| Delete keyword then create | Success (count decremented) |
| Soft-delete keyword then restore | Counts update correctly |
| Upgrade plan mid-session | New limits apply immediately |
Credit Deduction Tests
| Test Case | Expected Result |
|---|---|
| Generate content with sufficient credits | Content created, credits deducted |
| Generate content with insufficient credits | Error: "Insufficient credits" |
| Generate content at exact credit balance | Success, balance = 0 |
| Generate multiple items in queue | Each deducts credits sequentially |
| Credit deduction failure mid-operation | Transaction rolled back, no partial deduction |
Plan Limit Tests
| Plan | Test Case | Expected Result |
|---|---|---|
| Free | Create 101st keyword (limit: 100) | Blocked |
| Starter | Create 6 queue items (limit: 5) | Blocked |
| Growth | Enable automation | Success (has access) |
| Pro | Create unlimited keywords | Success (no limit) |
| Enterprise | All operations | No limits enforced |
Pricing Page Updates
Location: frontend/src/pages/marketing/Pricing.tsx
Required Elements
-
Plan Comparison Table
- All tiers side-by-side
- Feature checkmarks
- Highlight "Most Popular" plan
- Monthly/Annual toggle with savings badge
-
Usage Limits Display
- Show key limits per plan
- Use "Unlimited" label for null limits
- Tooltip explanations for complex limits
-
Credit System Explanation
- What credits are
- How they're consumed
- How to buy more
- Credit rollover rules
-
FAQ Section
- "What happens when I run out of credits?"
- "Can I change plans mid-month?"
- "Do unused credits roll over?"
- "What's included in Enterprise?"
-
Calculator Widget (Optional)
- Estimate monthly usage
- Recommend plan based on needs
- Show credit consumption breakdown
Success Metrics
- ✅ All AI operations enforce credit checks
- ✅ All create operations enforce limit checks
- ✅ Credit costs reflect actual provider costs + margin
- ✅ Plans are competitively priced
- ✅ Usage dashboard shows accurate counts
- ✅ Limit warnings prevent user frustration
- ✅ Upgrade flow is clear and frictionless
- ✅ Admin can adjust costs without code changes
- ✅ All tests pass
Related Files Reference
Backend
backend/igny8_core/auth/models.py- Account, Plan modelsbackend/igny8_core/business/billing/models.py- Credit modelsbackend/igny8_core/business/billing/constants.py- Credit costsbackend/igny8_core/business/billing/services/credit_service.py- Credit logicbackend/igny8_core/business/billing/services/limit_service.py- NEW Limit enforcementbackend/igny8_core/modules/planner/views.py- Planner API viewsbackend/igny8_core/modules/writer/views.py- Writer API views
Frontend
frontend/src/components/dashboard/UsageLimitsWidget.tsx- NEW Usage displayfrontend/src/pages/marketing/Pricing.tsx- Pricing pagefrontend/src/pages/Planner/*.tsx- Planner pages (add limit checks)frontend/src/pages/Writer/*.tsx- Writer pages (add limit checks)frontend/src/services/api.ts- API service (handle limit errors)
Notes
- Limits should be enforced at API level, not just UI level
- Consider "soft limits" with warnings vs "hard limits" with blocks
- Credit expiration and rollover rules need business decision
- Enterprise pricing needs custom quote system
- Monitor actual usage patterns to optimize costs and limits
- A/B test different pricing tiers to maximize conversion