Skip to main content
Copy-paste patterns for deducting credits from subscribers.

Automatic Credit Deduction

When using the standard Bearer token flow, credits are automatically deducted when you validate the request:
const result = await payments.requests.isValidRequest(token, body)

// Credits are automatically deducted based on plan configuration
// result.balance shows remaining credits AFTER deduction
console.log(`Remaining credits: ${result.balance}`)

Variable Credit Charges

For operations with variable costs, use the x402 flow:
// Calculate cost based on request
function calculateCost(request: any): number {
  const baseCredits = 1

  // Add cost for complexity
  if (request.options?.highQuality) {
    return baseCredits + 5
  }

  if (request.prompt.length > 1000) {
    return baseCredits + 2
  }

  return baseCredits
}

// Use in settlement
const cost = calculateCost(req.body)

await payments.facilitator.settlePermissions({
  planId: PLAN_ID,
  maxAmount: BigInt(cost),
  x402AccessToken: token,
  subscriberAddress: address
})

Credit Tracking Middleware

Track credits used per request:
interface CreditUsage {
  requestId: string
  subscriberAddress: string
  creditsUsed: number
  creditsBefore: number
  creditsAfter: number
  timestamp: Date
  endpoint: string
}

async function trackCredits(
  req: Request,
  res: Response,
  next: NextFunction
) {
  const startBalance = req.payment?.balance || 0

  // Store original json method
  const originalJson = res.json.bind(res)

  // Override to track response
  res.json = (body: any) => {
    const creditsUsed = body.credits?.used || 1
    const creditsAfter = body.credits?.remaining || startBalance - creditsUsed

    const usage: CreditUsage = {
      requestId: req.id,
      subscriberAddress: req.payment?.subscriberAddress || 'unknown',
      creditsUsed,
      creditsBefore: startBalance,
      creditsAfter,
      timestamp: new Date(),
      endpoint: req.path
    }

    // Log or store usage
    logCreditUsage(usage)

    return originalJson(body)
  }

  next()
}

function logCreditUsage(usage: CreditUsage) {
  console.log(`Credit usage: ${JSON.stringify(usage)}`)
  // Or send to analytics, database, etc.
}

Tiered Pricing

Charge different amounts based on usage tiers:
interface PricingTier {
  name: string
  minTokens: number
  maxTokens: number
  creditsPerRequest: number
}

const PRICING_TIERS: PricingTier[] = [
  { name: 'small', minTokens: 0, maxTokens: 100, creditsPerRequest: 1 },
  { name: 'medium', minTokens: 101, maxTokens: 500, creditsPerRequest: 3 },
  { name: 'large', minTokens: 501, maxTokens: 2000, creditsPerRequest: 5 },
  { name: 'xlarge', minTokens: 2001, maxTokens: Infinity, creditsPerRequest: 10 }
]

function getCreditsForTokens(tokenCount: number): number {
  const tier = PRICING_TIERS.find(
    t => tokenCount >= t.minTokens && tokenCount <= t.maxTokens
  )
  return tier?.creditsPerRequest || 1
}

// Usage
async function processWithTieredPricing(prompt: string, payment: PaymentInfo) {
  const estimatedTokens = estimateTokens(prompt)
  const requiredCredits = getCreditsForTokens(estimatedTokens)

  if (payment.balance < requiredCredits) {
    throw new Error(`Insufficient credits. Need ${requiredCredits}, have ${payment.balance}`)
  }

  const result = await generateResponse(prompt)
  const actualTokens = countTokens(result)
  const actualCredits = getCreditsForTokens(actualTokens)

  return {
    result,
    credits: {
      estimated: requiredCredits,
      actual: actualCredits,
      remaining: payment.balance - actualCredits
    }
  }
}

Pre-Authorization Pattern

For long-running operations, pre-authorize credits before starting:
async function processLongRunningTask(
  token: string,
  body: any,
  estimatedCredits: number
) {
  // Step 1: Validate and check balance
  const validation = await payments.requests.isValidRequest(token, body)

  if (!validation.isValid) {
    throw new Error('Invalid token')
  }

  if (validation.balance < estimatedCredits) {
    throw new Error(`Insufficient credits. Need ${estimatedCredits}, have ${validation.balance}`)
  }

  // Step 2: Start the long-running task
  const taskId = await startTask(body)

  try {
    // Step 3: Wait for completion
    const result = await waitForCompletion(taskId)

    // Step 4: Calculate actual cost and charge
    const actualCredits = calculateActualCost(result)

    // Note: For x402 flow, settlement happens here
    // For standard flow, credits were already deducted during validation

    return {
      result,
      credits: {
        estimated: estimatedCredits,
        actual: actualCredits,
        remaining: validation.balance - actualCredits
      }
    }
  } catch (error) {
    // Task failed - don't charge (or refund in x402 flow)
    console.error('Task failed:', error)
    throw error
  }
}

Credit Response Headers

Include credit information in response headers:
function addCreditHeaders(res: Response, credits: CreditInfo) {
  res.setHeader('X-Credits-Used', credits.used.toString())
  res.setHeader('X-Credits-Remaining', credits.remaining.toString())
  res.setHeader('X-Credits-Plan', credits.planId)
}

// Usage in middleware
app.use((req, res, next) => {
  const originalJson = res.json.bind(res)

  res.json = (body: any) => {
    if (req.payment && body.credits) {
      addCreditHeaders(res, body.credits)
    }
    return originalJson(body)
  }

  next()
})

Next Steps