Skip to main content
Manage billing, track usage, and monitor rate limits for Better Hub’s AI features and GitHub API calls.

GET /api/billing/balance

Get the user’s current credit balance and expiration information.

Response

available
number
Available credits (USD equivalent)
totalGranted
number
Total credits granted to user (USD equivalent)
nearestExpiry
string
ISO 8601 timestamp of the nearest credit expirationReturns null if no credits are expiring
welcomed
boolean
Whether user has received welcome credits
const response = await fetch('/api/billing/balance');
const balance = await response.json();

console.log(balance);
/*
{
  available: 5.75,
  totalGranted: 10.00,
  nearestExpiry: "2024-02-15T00:00:00Z",
  welcomed: true
}
*/
Credits may have expiration dates. Check nearestExpiry to warn users about expiring credits.

GET /api/billing/spending-limit

Get current spending limit configuration and usage for the billing period.

Response

Credit Mode (when user has credits):
mode
string
Billing mode: "credit"
available
number
Available credits (USD)
totalGranted
number
Total granted credits (USD)
monthlyCapUsd
number
Monthly spending cap (USD)Default: 10.00
periodUsageUsd
number
Current period usage (USD)
periodStart
string
Period start date (ISO 8601)
Subscription Mode (when user has subscription):
mode
string
Billing mode: "subscription"
monthlyCapUsd
number
Monthly spending cap (USD)
periodUsageUsd
number
Current period usage (USD)
periodStart
string
Subscription period start (ISO 8601)
remainingUsd
number
Remaining budget for current period (USD)
const response = await fetch('/api/billing/spending-limit');
const limit = await response.json();

console.log(limit);
/*
{
  mode: "credit",
  available: 5.75,
  totalGranted: 10.00,
  monthlyCapUsd: 10.00,
  periodUsageUsd: 3.25,
  periodStart: "2024-01-01T00:00:00Z"
}
*/

PATCH /api/billing/spending-limit

Update the monthly spending cap.
monthlyCapUsd
number
required
New monthly cap in USDSet to null to remove cap (unlimited)

Response

monthlyCapUsd
number
Updated monthly cap value
const response = await fetch('/api/billing/spending-limit', {
  method: 'PATCH',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    monthlyCapUsd: 25.00
  })
});

const updated = await response.json();
console.log('New cap:', updated.monthlyCapUsd); // 25.00
Spending caps protect against unexpected charges. Set a reasonable cap based on your expected usage.

GET /api/rate-limit

Get current GitHub API rate limit status.

Response

limit
number
Maximum requests allowed per hour
remaining
number
Requests remaining in current window
used
number
Requests used in current window
resetAt
number
Unix timestamp (seconds) when rate limit resets
const response = await fetch('/api/rate-limit');
const rateLimit = await response.json();

console.log(rateLimit);
/*
{
  limit: 5000,
  remaining: 4847,
  used: 153,
  resetAt: 1705329600
}
*/

Understanding Rate Limits

GitHub API Limits

Authenticated requests have different limits based on the auth method:
  • OAuth Token: 5,000 requests/hour
  • Personal Access Token: 5,000 requests/hour
  • Unauthenticated: 60 requests/hour

AI Usage Limits

AI features are rate limited by:
  1. Monthly Spending Cap: Configurable via /api/billing/spending-limit
  2. Credit Balance: Available credits via /api/billing/balance
  3. Per-request Costs: Varies by model and token usage

Common Rate Limit Errors

GitHub API (429):
{
  "error": "API rate limit exceeded",
  "resetAt": 1705329600
}
AI Usage (429):
{
  "error": "RATE_LIMIT_EXCEEDED",
  "allowed": false,
  "reason": "rate_limit",
  "periodUsageUsd": 15.50,
  "monthlyCapUsd": 10.00
}

Billing Error Codes

Error Types

  • RATE_LIMIT_EXCEEDED - Monthly spending cap reached
  • INSUFFICIENT_CREDITS - No credits remaining
  • SUBSCRIPTION_REQUIRED - Feature requires paid plan
  • INVALID_API_KEY - Custom API key invalid

Handling Errors

async function handleAIRequest(requestFn: () => Promise<Response>) {
  try {
    const response = await requestFn();
    
    if (response.status === 429) {
      const error = await response.json();
      
      switch (error.error) {
        case 'RATE_LIMIT_EXCEEDED':
          return {
            success: false,
            message: `Monthly spending cap reached ($${error.monthlyCapUsd}). ` +
                    `Current usage: $${error.periodUsageUsd}`,
            canRetry: false
          };
          
        case 'INSUFFICIENT_CREDITS':
          return {
            success: false,
            message: 'No credits remaining. Please purchase more credits.',
            canRetry: false
          };
          
        default:
          return {
            success: false,
            message: 'Rate limit exceeded. Please try again later.',
            canRetry: true
          };
      }
    }
    
    return { success: true, response };
  } catch (error) {
    return {
      success: false,
      message: 'Request failed',
      canRetry: true
    };
  }
}

Usage Tracking

Monitor Spending

interface UsageStats {
  currentPeriod: {
    used: number;
    cap: number;
    remaining: number;
    percentUsed: number;
  };
  credits: {
    available: number;
    total: number;
    expiringSoon: boolean;
  };
  github: {
    remaining: number;
    limit: number;
    percentUsed: number;
  };
}

async function getUsageStats(): Promise<UsageStats> {
  const [spending, balance, rateLimit] = await Promise.all([
    fetch('/api/billing/spending-limit').then(r => r.json()),
    fetch('/api/billing/balance').then(r => r.json()),
    fetch('/api/rate-limit').then(r => r.json())
  ]);
  
  const now = new Date();
  const nearExpiry = balance.nearestExpiry 
    ? new Date(balance.nearestExpiry)
    : null;
  const daysUntilExpiry = nearExpiry
    ? Math.ceil((nearExpiry.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
    : Infinity;
  
  return {
    currentPeriod: {
      used: spending.periodUsageUsd,
      cap: spending.monthlyCapUsd,
      remaining: spending.mode === 'subscription' 
        ? spending.remainingUsd 
        : spending.available,
      percentUsed: (spending.periodUsageUsd / spending.monthlyCapUsd) * 100
    },
    credits: {
      available: balance.available,
      total: balance.totalGranted,
      expiringSoon: daysUntilExpiry <= 7
    },
    github: {
      remaining: rateLimit.remaining,
      limit: rateLimit.limit,
      percentUsed: (rateLimit.used / rateLimit.limit) * 100
    }
  };
}

// Display usage dashboard
const stats = await getUsageStats();
console.log('📊 Usage Statistics:');
console.log(`AI: $${stats.currentPeriod.used.toFixed(2)}/$${stats.currentPeriod.cap.toFixed(2)} (${stats.currentPeriod.percentUsed.toFixed(1)}%)`);
console.log(`Credits: $${stats.credits.available.toFixed(2)} available`);
console.log(`GitHub API: ${stats.github.remaining}/${stats.github.limit} requests remaining`);

if (stats.credits.expiringSoon) {
  console.warn('⚠️ Credits expiring soon!');
}