Skip to content

Credit System Documentation

Overview

AppVector uses a dynamic credit system built on top of the Feature framework. Credits are consumable resources that track API calls, data scrapes, and other metered usage. The system is fully flexible - new credit types can be added without code changes.

Architecture

How Credits Work

Credits are implemented as CREDIT_BASED features in the dynamic feature system:

  • Feature: Defines a credit type (e.g., "API Calls", "Data Scrapes")
  • FeaturePlan: Sets how many credits each plan gets per period
  • FeatureUsage: Tracks consumption and balances per workspace

Credit Components

Each credit balance consists of:

  1. Allocated Credits: Monthly/periodic credits from your plan
  2. Purchased Credits: Additional credits you buy (roll over)
  3. Bonus Credits: One-time credits given by admin (don't reset)
  4. Consumed Credits: How many you've used this period

Available Credits = Allocated + Purchased + Bonus - Consumed

Reset Periods

Credits can reset on different schedules: - DAILY: Reset every 24 hours - WEEKLY: Reset every 7 days - MONTHLY: Reset on billing cycle (most common) - NEVER: Never reset (for purchased credits)

API Endpoints

Get Credit Balances

GET /api/billing/credits/?workspace_id={workspace_id}

Returns all credit balances for a workspace:

{
  "balances": [
    {
      "feature_key": "api_calls",
      "feature_name": "API Calls",
      "category": "usage",
      "allocated": 5000,
      "consumed": 1234,
      "purchased": 500,
      "bonus": 100,
      "available": 4366,
      "usage_percentage": 21.9,
      "period_start": "2024-01-01T00:00:00Z",
      "period_end": "2024-02-01T00:00:00Z",
      "reset_period": "MONTHLY"
    },
    {
      "feature_key": "data_scrapes",
      "feature_name": "Data Scrapes",
      "allocated": 1000,
      "consumed": 450,
      "available": 550,
      "usage_percentage": 45.0
    }
  ],
  "summary": {
    "total_features": 2,
    "total_consumed": 1684,
    "total_available": 4916,
    "features_near_limit": 0
  }
}

Consume Credits

POST /api/billing/credits/consume/

Request body:

{
  "workspace_id": "uuid",
  "feature_key": "api_calls",
  "amount": 10,
  "metadata": {
    "endpoint": "/api/apps/search",
    "request_id": "req_123"
  }
}

Response:

{
  "success": true,
  "remaining": 4356,
  "consumed": 10,
  "feature_key": "api_calls"
}

Add Bonus Credits (Admin)

POST /api/billing/credits/bonus/

Request body:

{
  "workspace_id": "uuid",
  "feature_key": "api_calls",
  "amount": 1000,
  "reason": "Compensation for service outage"
}

Get Usage History

GET /api/billing/credits/history/?workspace_id={workspace_id}&days=30

Returns historical credit usage for analysis.

Credit Types

Standard Credit Features

These are the default credit types configured in the system:

Feature Key Name Description Reset
api_calls API Calls REST API requests Monthly
data_scrapes Data Scrapes App store data fetches Monthly
keyword_tracking Keyword Tracking Keyword rank checks Monthly
competitor_tracking Competitor Tracking Competitor analysis Monthly
ai_analysis AI Analysis AI-powered insights Monthly
data_exports Data Exports CSV/JSON exports Monthly

Plan Allocations

Plan API Calls Data Scrapes Keyword Tracking AI Analysis
FREE 100/mo 50/mo 10/mo 0
STARTER 1,000/mo 500/mo 100/mo 10/mo
PROFESSIONAL 5,000/mo 2,500/mo 500/mo 100/mo
ENTERPRISE 20,000/mo 10,000/mo 2,000/mo 1,000/mo
INTERNAL Unlimited Unlimited Unlimited Unlimited

Integration Guide

Checking Credits Before Operations

# In your service layer
from services.billing import DynamicFeatureService

def scrape_app_data(workspace, app_id):
    # Check if enough credits
    feature_key = 'data_scrapes'
    required_credits = 1

    available = DynamicFeatureService.get_feature_value(
        workspace, feature_key
    )

    if available < required_credits:
        raise InsufficientCreditsError(
            f"Need {required_credits} credits, only {available} available"
        )

    # Consume credits
    success, remaining, usage = DynamicFeatureService.consume_credits(
        workspace=workspace,
        feature_key=feature_key,
        amount=required_credits,
        metadata={'app_id': app_id}
    )

    if not success:
        raise InsufficientCreditsError("Failed to consume credits")

    # Proceed with operation
    data = perform_scrape(app_id)
    return data

Adding New Credit Types

  1. Create a new Feature via Django admin:

    Feature.objects.create(
        key='new_credit_type',
        name='New Credit Type',
        description='Description of what this credit is for',
        feature_type=FeatureType.CREDIT_BASED,
        category='usage',
        reset_period=ResetPeriod.MONTHLY,
        is_active=True
    )
    

  2. Link to plans via FeaturePlan:

    for plan in Plan.objects.all():
        FeaturePlan.objects.create(
            plan=plan,
            feature=feature,
            allocated_amount=1000,  # Credits per month
            custom_reset_period=ResetPeriod.MONTHLY
        )
    

Credit Purchasing

One-Time Credit Purchase Flow

  1. User needs more credits
  2. Frontend calls: POST /api/billing/stripe/checkout/credits/
  3. User redirected to Stripe
  4. Payment processed
  5. Webhook adds credits to purchased_amount
  6. Credits immediately available

Pricing

  • API Calls: $0.01 per credit
  • Data Scrapes: $0.05 per credit
  • AI Analysis: $0.10 per credit
  • Custom pricing available for Enterprise

Best Practices

1. Always Check Before Consuming

Never assume credits are available. Always check first:

if not DynamicFeatureService.check_feature_available(workspace, 'api_calls'):
    return error_response("Feature not available for your plan")

2. Use Metadata for Tracking

Include relevant metadata when consuming credits:

metadata = {
    'user_id': request.user.id,
    'endpoint': request.path,
    'ip_address': request.META.get('REMOTE_ADDR'),
    'timestamp': timezone.now().isoformat()
}

3. Handle Insufficient Credits Gracefully

try:
    success, remaining, usage = DynamicFeatureService.consume_credits(...)
    if not success:
        # Offer to purchase more credits
        return Response({
            'error': 'Insufficient credits',
            'remaining': remaining,
            'purchase_url': '/billing/credits/purchase'
        }, status=402)  # Payment Required
except Exception as e:
    logger.error(f"Credit consumption failed: {e}")
    # Allow operation but log for investigation
    pass

4. Monitor Usage Patterns

Set up alerts when workspaces approach limits:

for usage in FeatureUsage.objects.filter(
    feature__feature_type=FeatureType.CREDIT_BASED
):
    if usage.usage_percentage() > 80:
        send_credit_warning_email(usage.workspace)

Migration from Legacy System

If migrating from the old CreditBalance/CreditTransaction models:

  1. Map old credit types to features:
  2. CreditType.API_CALL → 'api_calls' feature
  3. CreditType.DATA_SCRAPE → 'data_scrapes' feature

  4. Migrate balances:

    for old_balance in CreditBalance.objects.all():
        feature = Feature.objects.get(key=map_credit_type(old_balance.credit_type))
        FeatureUsage.objects.create(
            workspace=old_balance.workspace,
            feature=feature,
            allocated_amount=old_balance.allocated_credits,
            consumed_amount=old_balance.consumed_credits,
            purchased_amount=old_balance.purchased_credits,
            bonus_amount=old_balance.bonus_credits,
            period_start=old_balance.period_start,
            period_end=old_balance.period_end
        )
    

  5. Update service calls:

  6. Replace CreditService.consume() with DynamicFeatureService.consume_credits()
  7. Replace CreditBalance.available_credits() with FeatureUsage.available_amount()

Troubleshooting

Credits Not Resetting

Check the reset period configuration:

usage = FeatureUsage.objects.get(workspace=workspace, feature__key='api_calls')
print(f"Period ends: {usage.period_end}")
print(f"Needs reset: {usage.needs_reset()}")

Negative Credit Balance

This shouldn't happen with proper checking, but if it does:

usage.consumed_amount = min(
    usage.consumed_amount,
    usage.allocated_amount + usage.purchased_amount + usage.bonus_amount
)
usage.save()

Credit Consumption Not Tracked

Ensure the feature exists and is active:

Feature.objects.filter(key='api_calls', is_active=True).exists()

Webhooks

Credit-related webhook events:

  • payment_intent.succeeded - Adds purchased credits
  • invoice.payment_succeeded - Monthly subscription renewal resets credits
  • customer.subscription.updated - Plan change updates allocated credits

FAQ

Q: What happens to unused credits at the end of the period? A: Allocated credits don't roll over. Purchased and bonus credits do.

Q: Can I transfer credits between workspaces? A: No, credits are workspace-specific and non-transferable.

Q: How do I give free credits to a customer? A: Use the bonus credits endpoint (admin only) or create a coupon in Stripe.

Q: Can I set custom credit prices per customer? A: Yes, use custom FeaturePlan entries for enterprise customers.

Q: How do I track credit usage by user within a workspace? A: Include user_id in the metadata when consuming credits, then query by metadata.