GraphQL Credit Management

This page documents GraphQL operations for managing API credits, including checking balances, viewing packages, and purchasing additional credits.

Credit System Overview

Crane Ledger uses a credit-based billing system where API calls consume credits. Organizations can:

  • Check their current credit balance
  • View available credit packages
  • Purchase additional credits via Stripe checkout
  • Monitor credit usage and thresholds

Credit Balance Queries

Get Credit Balance

Check your organization's current credit status.

query {
  creditBalance {
    organizationId
    creditsRemaining
    creditsTotal
    autoRechargeEnabled
    lowBalanceThreshold
  }
}

Returns: CreditBalanceNode!

Access: Current organization's API key required

Response Example:

{
  "data": {
    "creditBalance": {
      "organizationId": "ORG_12345678901234567890123456789012",
      "creditsRemaining": 1250,
      "creditsTotal": 5000,
      "autoRechargeEnabled": false,
      "lowBalanceThreshold": 100
    }
  }
}

Real-time Balance Checking

// Check balance before making expensive operations
async function checkBalance() {
  const query = `
    query {
      creditBalance {
        creditsRemaining
        lowBalanceThreshold
      }
    }
  `;

  const response = await graphql.request(query);
  const balance = response.data.creditBalance;

  if (balance.creditsRemaining <= balance.lowBalanceThreshold) {
    showLowBalanceWarning();
  }

  return balance.creditsRemaining;
}

Credit Packages

View Available Packages

Get all available credit packages for purchase.

query {
  creditPackages {
    packageType
    credits
    name
    priceUsd
    priceCents
  }
}

Returns: [CreditPackageNode!]!

Access: Public (same packages for all organizations)

Response Example:

{
  "data": {
    "creditPackages": [
      {
        "packageType": "SOLO_DEV",
        "credits": 1000,
        "name": "Solo Dev - 1,000 credits",
        "priceUsd": 10.0,
        "priceCents": 1000
      },
      {
        "packageType": "BUILDER",
        "credits": 5000,
        "name": "Builder - 5,000 credits",
        "priceUsd": 50.0,
        "priceCents": 5000
      },
      {
        "packageType": "SCALE",
        "credits": 25000,
        "name": "Scale - 25,000 credits",
        "priceUsd": 250.0,
        "priceCents": 25000
      }
    ]
  }
}

Package Details

SOLO_DEV Package:

  • Credits: 1,000
  • Price: $10.00 ($0.01 per credit)
  • Best for: Individual developers, small projects

BUILDER Package:

  • Credits: 5,000
  • Price: $50.00 ($0.01 per credit)
  • Best for: Development teams, moderate usage

SCALE Package:

  • Credits: 25,000
  • Price: $250.00 ($0.01 per credit)
  • Best for: High-volume applications, production systems

Credit Purchase

Initiate Credit Purchase

Start the credit purchase process via Stripe checkout.

mutation PurchaseCredits(
  $creditAmount: Int!
  $successUrl: String
  $cancelUrl: String
) {
  purchaseCredits(
    creditAmount: $creditAmount
    successUrl: $successUrl
    cancelUrl: $cancelUrl
  ) {
    checkoutUrl
    creditsToAdd
    priceCents
    expiresAt
  }
}

Required Arguments:

  • creditAmount: Int! - Number of credits to purchase (must match available packages)

Optional Arguments:

  • successUrl: String - URL to redirect after successful payment
  • cancelUrl: String - URL to redirect if payment is cancelled

Returns: CreditPurchaseNode!

Process Flow:

  1. Mutation validates credit amount and organization
  2. Creates Stripe checkout session
  3. Returns checkout URL for payment completion
  4. User completes payment on Stripe
  5. Stripe webhooks credit the organization
  6. Credits become available immediately

Purchase Examples

Purchase Solo Dev Package

const purchaseSoloDev = async () => {
  const mutation = `
    mutation PurchaseCredits($creditAmount: Int!) {
      purchaseCredits(creditAmount: $creditAmount) {
        checkoutUrl
        creditsToAdd
        priceCents
      }
    }
  `;

  const response = await graphql.request(mutation, {
    creditAmount: 1000
  });

  // Redirect user to Stripe checkout
  window.location.href = response.data.purchaseCredits.checkoutUrl;
};

Custom Amount Purchase

const purchaseCustomCredits = async (amount) => {
  const mutation = `
    mutation PurchaseCredits($creditAmount: Int!, $successUrl: String) {
      purchaseCredits(
        creditAmount: $creditAmount
        successUrl: $successUrl
      ) {
        checkoutUrl
        creditsToAdd
        priceCents
        expiresAt
      }
    }
  `;

  const response = await graphql.request(mutation, {
    creditAmount: amount,
    successUrl: `${window.location.origin}/credits/purchased`
  });

  return response.data.purchaseCredits;
};

Handling Purchase Responses

const handleCreditPurchase = async (creditAmount) => {
  try {
    const result = await purchaseCredits(creditAmount);

    // Store checkout session info for tracking
    sessionStorage.setItem('checkout_session', JSON.stringify({
      creditsToAdd: result.creditsToAdd,
      expiresAt: result.expiresAt
    }));

    // Redirect to Stripe checkout
    window.location.href = result.checkoutUrl;

  } catch (error) {
    if (error.extensions?.code === 'VALIDATION_ERROR') {
      showError('Invalid credit amount. Please select a valid package.');
    } else {
      showError('Failed to initiate purchase. Please try again.');
    }
  }
};

Credit Usage Monitoring

Balance Monitoring

Monitor credit balance and usage patterns.

class CreditMonitor {
  constructor() {
    this.balance = null;
    this.threshold = 100; // Alert when below 100 credits
  }

  async checkBalance() {
    const query = `
      query {
        creditBalance {
          creditsRemaining
          lowBalanceThreshold
        }
      }
    `;

    const response = await graphql.request(query);
    this.balance = response.data.creditBalance;

    this.checkThresholds();
    return this.balance;
  }

  checkThresholds() {
    if (this.balance.creditsRemaining <= this.balance.lowBalanceThreshold) {
      this.showLowBalanceAlert();
    }

    if (this.balance.creditsRemaining <= 50) {
      this.showCriticalBalanceAlert();
    }
  }

  showLowBalanceAlert() {
    showNotification({
      type: 'warning',
      title: 'Low Credit Balance',
      message: `You have ${this.balance.creditsRemaining} credits remaining. Consider purchasing more.`,
      action: {
        text: 'Buy Credits',
        onClick: () => navigateToCreditsPage()
      }
    });
  }

  showCriticalBalanceAlert() {
    showNotification({
      type: 'error',
      title: 'Critical Credit Balance',
      message: `Only ${this.balance.creditsRemaining} credits remaining. API calls may fail.`,
      action: {
        text: 'Buy Credits Now',
        onClick: () => navigateToCreditsPage()
      }
    });
  }
}

Usage Tracking

Track credit consumption patterns.

const trackCreditUsage = async () => {
  // Get initial balance
  const initialBalance = await getCreditBalance();

  // Perform operations
  await performOperations();

  // Check final balance
  const finalBalance = await getCreditBalance();

  const creditsUsed = initialBalance.creditsRemaining - finalBalance.creditsRemaining;

  // Log usage
  analytics.track('credit_usage', {
    credits_used: creditsUsed,
    operations_performed: operationCount,
    average_cost_per_operation: creditsUsed / operationCount
  });

  return creditsUsed;
};

Credit Purchase Integration

React Component Example

function CreditPurchase({ onPurchaseComplete }) {
  const [packages, setPackages] = useState([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    loadCreditPackages();
  }, []);

  const loadCreditPackages = async () => {
    const query = `
      query {
        creditPackages {
          packageType
          credits
          name
          priceUsd
        }
      }
    `;

    const response = await graphql.request(query);
    setPackages(response.data.creditPackages);
  };

  const purchasePackage = async (creditAmount) => {
    setLoading(true);
    try {
      const mutation = `
        mutation PurchaseCredits($creditAmount: Int!) {
          purchaseCredits(creditAmount: $creditAmount) {
            checkoutUrl
          }
        }
      `;

      const response = await graphql.request(mutation, { creditAmount });

      // Redirect to Stripe checkout
      window.location.href = response.data.purchaseCredits.checkoutUrl;

    } catch (error) {
      showError('Purchase failed: ' + error.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="credit-packages">
      <h2>Purchase Credits</h2>
      {packages.map(pkg => (
        <div key={pkg.packageType} className="package-card">
          <h3>{pkg.name}</h3>
          <p>{pkg.credits} credits</p>
          <p>${pkg.priceUsd}</p>
          <button
            onClick={() => purchasePackage(pkg.credits)}
            disabled={loading}
          >
            {loading ? 'Processing...' : 'Purchase'}
          </button>
        </div>
      ))}
    </div>
  );
}

Success/Cancel Handling

// Handle successful purchase return
function handlePurchaseSuccess(sessionId) {
  // Verify purchase with backend
  verifyPurchase(sessionId).then(() => {
    showSuccess('Credits purchased successfully!');
    refreshCreditBalance();
    onPurchaseComplete?.();
  });
}

// Handle cancelled purchase
function handlePurchaseCancel() {
  showInfo('Purchase cancelled. No credits were charged.');
}

Credit Cost Estimation

Estimate Operation Costs

Calculate expected credit costs before operations.

const estimateCreditCost = (operations) => {
  const costEstimates = {
    // Basic operations
    create_account: 1,
    update_account: 1,
    delete_account: 1,

    // Transaction operations
    create_transaction: 2,
    get_transaction: 1,

    // Report operations
    generate_trial_balance: 5,
    generate_balance_sheet: 10,
    generate_income_statement: 10,

    // Document operations
    create_invoice: 3,
    create_bill: 3,
    get_invoice_pdf: 2,
  };

  return operations.reduce((total, op) => {
    return total + (costEstimates[op.type] || 1);
  }, 0);
};

// Usage
const operations = [
  { type: 'create_account' },
  { type: 'create_transaction' },
  { type: 'generate_trial_balance' }
];

const estimatedCost = estimateCreditCost(operations);
// Result: 8 credits

Budget Management

Implement spending limits and alerts.

class CreditBudget {
  constructor(maxSpendPerHour = 100) {
    this.maxSpendPerHour = maxSpendPerHour;
    this.hourlySpend = 0;
    this.lastReset = Date.now();
  }

  async checkBudget(operationCost) {
    this.resetIfNeeded();

    if (this.hourlySpend + operationCost > this.maxSpendPerHour) {
      throw new Error(`Budget exceeded. Would spend ${this.hourlySpend + operationCost} credits this hour.`);
    }

    return true;
  }

  recordSpend(amount) {
    this.hourlySpend += amount;
  }

  resetIfNeeded() {
    const now = Date.now();
    const hourInMs = 60 * 60 * 1000;

    if (now - this.lastReset >= hourInMs) {
      this.hourlySpend = 0;
      this.lastReset = now;
    }
  }
}

Error Handling

Purchase Errors

Common credit purchase error codes:

  • VALIDATION_ERROR - Invalid credit amount or parameters
  • INTERNAL_ERROR - Stripe configuration or API issues
  • FORBIDDEN - Organization not authorized for purchases

Balance Errors

  • INTERNAL_ERROR - Unable to retrieve balance
  • NOT_FOUND - Organization not found

Handling Errors

const handleCreditError = (error) => {
  switch (error.extensions?.code) {
    case 'VALIDATION_ERROR':
      showError('Invalid purchase amount. Please select a valid credit package.');
      break;

    case 'INTERNAL_ERROR':
      showError('Payment system temporarily unavailable. Please try again later.');
      break;

    case 'RATE_LIMIT_EXCEEDED':
      showError('Too many requests. Please wait a moment before trying again.');
      break;

    default:
      showError('An unexpected error occurred. Please contact support.');
  }
};

Best Practices

Credit Management

  1. Monitor Balance Regularly - Check balance before critical operations
  2. Set Low Balance Alerts - Configure notifications for low credit warnings
  3. Purchase in Advance - Buy credits before running out to avoid service interruption
  4. Track Usage Patterns - Monitor which operations consume the most credits
  5. Implement Budget Controls - Set spending limits for different environments

Purchase Process

  1. Validate Amounts - Ensure purchase amounts match available packages
  2. Handle Redirects - Implement proper success/cancel URL handling
  3. Verify Purchases - Confirm credits were added after successful payment
  4. Store Session Info - Track checkout sessions for debugging
  5. Error Recovery - Handle failed payments gracefully

Cost Optimization

  1. Batch Operations - Combine multiple operations in single requests
  2. Use Pagination - Limit result sets to reduce credit consumption
  3. Cache Results - Avoid repeated expensive queries
  4. Select Fields - Only request needed fields in GraphQL queries
  5. Monitor Usage - Track and analyze credit consumption patterns

Support and Troubleshooting

Common Issues

Purchase Not Completing:

  • Check Stripe checkout session hasn't expired
  • Verify payment method was accepted
  • Contact support with session ID

Credits Not Added:

  • Wait a few minutes for webhook processing
  • Check organization ID matches purchase
  • Contact support with transaction details

Balance Not Updating:

  • Refresh balance query
  • Check for cached results
  • Contact support if issue persists

Getting Help

For credit-related issues:

  • Check the GraphQL error response for specific error codes
  • Include organization ID and transaction details when contacting support
  • Provide timestamps and operation details for debugging

Need help?

Create a free account to access our support portal. Once signed in, use the Support tab in your dashboard to submit a support ticket — our team typically responds within 24 hours.