.do

Per-Use Pricing

Charge customers based on actual service usage

Per-use pricing charges customers based on actual consumption - every API call, transaction, word, or analysis. This model aligns costs with value delivered and scales naturally with customer success.

When to Use Per-Use Pricing

Best for:

  • Variable usage patterns
  • Clear, measurable units (API calls, words, images)
  • Services where value scales with volume
  • Customers who want cost predictability
  • Services with low fixed costs

Not ideal for:

  • High customer acquisition costs
  • Services requiring significant upfront work
  • Unpredictable resource consumption
  • Complex value calculations

Core Patterns

Pattern 1: Simple Per-Unit

Charge a fixed rate per unit of service:

import $, { ai, on, send } from 'sdk.do'

const translationService = await $.Service.create({
  name: 'Document Translation',
  type: $.ServiceType.Translation,

  pricing: {
    model: 'per-unit',
    unit: 'word',
    rate: 0.02, // $0.02 per word
    minimumCharge: 5.0, // Minimum $5 per request
  },
})

on($.ServiceRequest.created, async (request) => {
  if (request.serviceId !== translationService.id) return

  try {
    // Extract document text
    const text = request.inputs.document
    const wordCount = countWords(text)

    // Translate
    const translated = await ai.translate({
      model: 'gpt-5',
      text,
      from: request.inputs.sourceLang,
      to: request.inputs.targetLang,
    })

    // Calculate cost
    const cost = Math.max(wordCount * translationService.pricing.rate, translationService.pricing.minimumCharge)

    // Deliver result
    await send($.ServiceResult.deliver, {
      requestId: request.id,
      outputs: {
        translatedText: translated.text,
        wordCount,
        detectedLanguage: translated.detectedSourceLang,
      },
    })

    // Charge customer
    await send($.Payment.charge, {
      customerId: request.customerId,
      amount: cost,
      description: `Translation: ${wordCount} words`,
      breakdown: {
        words: wordCount,
        rate: translationService.pricing.rate,
        subtotal: wordCount * translationService.pricing.rate,
        minimum: translationService.pricing.minimumCharge,
        charged: cost,
      },
    })
  } catch (error) {
    await send($.ServiceRequest.fail, {
      requestId: request.id,
      error: error.message,
    })
  }
})

function countWords(text: string): number {
  return text.trim().split(/\s+/).length
}

Key Benefits:

  • Simple to understand
  • Easy to calculate
  • Fair pricing for all customers
  • Scales automatically

Example Pricing:

  • 1,000 words = $20
  • 5,000 words = $100
  • 10,000 words = $200

Pattern 2: Per-API-Call

Charge for each service invocation:

const imageGenerationService = await $.Service.create({
  name: 'AI Image Generator',
  type: $.ServiceType.ImageGeneration,

  pricing: {
    model: 'per-call',
    tiers: {
      standard: {
        rate: 2.0, // $2 per image
        resolution: '1024x1024',
      },
      hd: {
        rate: 4.0, // $4 per HD image
        resolution: '2048x2048',
      },
      ultra: {
        rate: 8.0, // $8 per ultra-HD image
        resolution: '4096x4096',
      },
    },
  },
})

on($.ServiceRequest.created, async (request) => {
  if (request.serviceId !== imageGenerationService.id) return

  const quality = request.inputs.quality || 'standard'
  const tier = imageGenerationService.pricing.tiers[quality]

  try {
    // Generate image
    const image = await ai.generateImage({
      model: 'dalle-3',
      prompt: request.inputs.prompt,
      resolution: tier.resolution,
      quality,
    })

    // Deliver
    await send($.ServiceResult.deliver, {
      requestId: request.id,
      outputs: {
        imageUrl: image.url,
        resolution: tier.resolution,
        prompt: request.inputs.prompt,
      },
    })

    // Charge
    await send($.Payment.charge, {
      customerId: request.customerId,
      amount: tier.rate,
      description: `Image Generation (${quality})`,
      breakdown: {
        quality,
        resolution: tier.resolution,
        rate: tier.rate,
      },
    })
  } catch (error) {
    await send($.ServiceRequest.fail, {
      requestId: request.id,
      error: error.message,
    })
  }
})

Example Pricing:

  • Standard (1024x1024): $2/image
  • HD (2048x2048): $4/image
  • Ultra (4096x4096): $8/image

Pattern 3: Per-Transaction

Charge based on business transactions processed:

const invoiceProcessingService = await $.Service.create({
  name: 'Automated Invoice Processor',
  type: $.ServiceType.Automation,

  pricing: {
    model: 'per-transaction',
    base: 1.5, // $1.50 per invoice
    modifiers: {
      complexity: {
        simple: 1.0,
        standard: 1.5,
        complex: 2.5,
      },
      pages: {
        perPage: 0.25, // $0.25 per additional page
        included: 2, // First 2 pages included
      },
    },
  },
})

on($.Invoice.received, async (invoice) => {
  try {
    // Extract data
    const extracted = await ai.extract({
      model: 'gpt-5',
      document: invoice.file,
      schema: {
        vendor: 'string',
        invoiceNumber: 'string',
        date: 'date',
        total: 'currency',
        lineItems: ['object'],
      },
    })

    // Determine complexity
    const complexity = determineComplexity(extracted)
    const pageCount = invoice.pageCount || 1

    // Calculate price
    const pricing = invoiceProcessingService.pricing
    const complexityMultiplier = pricing.modifiers.complexity[complexity]
    const additionalPages = Math.max(0, pageCount - pricing.modifiers.pages.included)
    const pageCost = additionalPages * pricing.modifiers.pages.perPage

    const total = pricing.base * complexityMultiplier + pageCost

    // Process invoice (validation, routing, etc.)
    await processInvoice(extracted)

    // Charge
    await send($.Payment.charge, {
      customerId: invoice.companyId,
      amount: total,
      description: `Invoice Processing - ${extracted.invoiceNumber}`,
      breakdown: {
        base: pricing.base,
        complexity: { level: complexity, multiplier: complexityMultiplier },
        pages: { count: pageCount, additional: additionalPages, cost: pageCost },
        total,
      },
    })
  } catch (error) {
    await send($.Invoice.processingFailed, {
      invoiceId: invoice.id,
      error: error.message,
    })
  }
})

function determineComplexity(invoice: any): 'simple' | 'standard' | 'complex' {
  const lineItems = invoice.lineItems?.length || 0
  const hasMultipleTaxes = (invoice.taxes?.length || 0) > 1
  const hasDiscounts = invoice.discounts && invoice.discounts.length > 0

  if (lineItems > 20 || hasMultipleTaxes || hasDiscounts) return 'complex'
  if (lineItems > 5) return 'standard'
  return 'simple'
}

Example Pricing:

  • Simple invoice (1 page, ≤5 items): $1.50
  • Standard invoice (2 pages, 6-20 items): $2.25
  • Complex invoice (4 pages, >20 items): $4.25

Pattern 4: Per-Analysis

Charge for analytical services:

const sentimentAnalysisService = await $.Service.create({
  name: 'Customer Feedback Analyzer',
  type: $.ServiceType.Analysis,

  pricing: {
    model: 'per-analysis',
    rates: {
      quickScan: {
        rate: 0.05, // $0.05 per review
        includes: ['sentiment', 'score'],
      },
      standard: {
        rate: 0.15, // $0.15 per review
        includes: ['sentiment', 'score', 'themes', 'keywords'],
      },
      deep: {
        rate: 0.35, // $0.35 per review
        includes: ['sentiment', 'score', 'themes', 'keywords', 'emotions', 'intent', 'recommendations'],
      },
    },
    bulk: {
      discount: true,
      tiers: [
        { min: 100, discount: 0.1 }, // 10% off for 100+
        { min: 1000, discount: 0.2 }, // 20% off for 1000+
        { min: 10000, discount: 0.3 }, // 30% off for 10000+
      ],
    },
  },
})

on($.ServiceRequest.created, async (request) => {
  if (request.serviceId !== sentimentAnalysisService.id) return

  const analysisLevel = request.inputs.level || 'standard'
  const reviews = request.inputs.reviews
  const pricing = sentimentAnalysisService.pricing.rates[analysisLevel]

  try {
    const results = []

    // Analyze each review
    for (const review of reviews) {
      const analysis = await ai.analyze({
        model: 'gpt-5',
        text: review.text,
        extract: pricing.includes,
        outputFormat: 'structured',
      })

      results.push({
        reviewId: review.id,
        ...analysis,
      })
    }

    // Calculate cost with bulk discount
    let unitCost = pricing.rate
    const quantity = reviews.length

    const bulkTier = sentimentAnalysisService.pricing.bulk.tiers.filter((t) => quantity >= t.min).pop()

    if (bulkTier) {
      unitCost = pricing.rate * (1 - bulkTier.discount)
    }

    const total = quantity * unitCost

    // Deliver
    await send($.ServiceResult.deliver, {
      requestId: request.id,
      outputs: {
        analyses: results,
        summary: {
          totalReviews: quantity,
          averageSentiment: calculateAverage(results, 'sentiment'),
          topThemes: extractTopThemes(results),
          distribution: calculateDistribution(results),
        },
      },
    })

    // Charge
    await send($.Payment.charge, {
      customerId: request.customerId,
      amount: total,
      description: `Sentiment Analysis (${analysisLevel}) - ${quantity} reviews`,
      breakdown: {
        level: analysisLevel,
        quantity,
        baseRate: pricing.rate,
        discount: bulkTier ? bulkTier.discount : 0,
        unitCost,
        total,
      },
    })
  } catch (error) {
    await send($.ServiceRequest.fail, {
      requestId: request.id,
      error: error.message,
    })
  }
})

Example Pricing:

  • Quick scan: $0.05/review ($50 for 1,000)
  • Standard: $0.15/review ($120 for 1,000 with 20% discount)
  • Deep analysis: $0.35/review ($245 for 1,000 with 30% discount)

Pattern 5: Composite Metering

Charge based on multiple usage dimensions:

const contentOptimizationService = await $.Service.create({
  name: 'Multi-Channel Content Optimizer',
  type: $.ServiceType.ContentOptimization,

  pricing: {
    model: 'composite',
    components: {
      wordAnalysis: {
        rate: 0.01, // $0.01 per word analyzed
      },
      seoOptimization: {
        rate: 5.0, // $5 per SEO optimization
      },
      imageSuggestions: {
        rate: 1.0, // $1 per image suggested
      },
      competitorAnalysis: {
        rate: 10.0, // $10 per competitor analyzed
      },
      readabilityCheck: {
        rate: 2.0, // $2 per check
      },
    },
  },
})

on($.ServiceRequest.created, async (request) => {
  if (request.serviceId !== contentOptimizationService.id) return

  const content = request.inputs.content
  const wordCount = countWords(content)
  const pricing = contentOptimizationService.pricing.components

  const costs = {}

  try {
    // Word analysis (always included)
    const analysis = await ai.analyze({
      model: 'gpt-5',
      text: content,
      extract: ['keywords', 'topics', 'structure'],
    })
    costs.wordAnalysis = wordCount * pricing.wordAnalysis.rate

    // SEO optimization (if requested)
    let seoResults
    if (request.inputs.includeSEO) {
      seoResults = await ai.optimize({
        content,
        type: 'seo',
        targetKeywords: request.inputs.keywords,
      })
      costs.seoOptimization = pricing.seoOptimization.rate
    }

    // Image suggestions (if requested)
    let images
    if (request.inputs.suggestImages) {
      images = await ai.suggestImages({
        content,
        count: request.inputs.imageCount || 3,
      })
      costs.imageSuggestions = images.length * pricing.imageSuggestions.rate
    }

    // Competitor analysis (if requested)
    let competitors
    if (request.inputs.analyzeCompetitors) {
      competitors = await analyzeCompetitors({
        content,
        competitors: request.inputs.competitorUrls || [],
      })
      costs.competitorAnalysis = (request.inputs.competitorUrls?.length || 0) * pricing.competitorAnalysis.rate
    }

    // Readability check (if requested)
    let readability
    if (request.inputs.checkReadability) {
      readability = await ai.evaluate({
        content,
        metrics: ['readability', 'grade-level', 'complexity'],
      })
      costs.readabilityCheck = pricing.readabilityCheck.rate
    }

    // Calculate total
    const total = Object.values(costs).reduce((sum, cost) => sum + cost, 0)

    // Deliver
    await send($.ServiceResult.deliver, {
      requestId: request.id,
      outputs: {
        analysis,
        seo: seoResults,
        images,
        competitors,
        readability,
        metrics: {
          wordCount,
          servicesUsed: Object.keys(costs),
        },
      },
    })

    // Charge
    await send($.Payment.charge, {
      customerId: request.customerId,
      amount: total,
      description: 'Content Optimization',
      breakdown: costs,
    })
  } catch (error) {
    await send($.ServiceRequest.fail, {
      requestId: request.id,
      error: error.message,
    })
  }
})

Example Pricing:

  • 1,000 words + SEO + 3 images + readability = $18
    • Word analysis: $10 (1,000 × $0.01)
    • SEO: $5
    • Images: $3 (3 × $1)
    • Readability: $2

Advanced Patterns

Usage Aggregation

Track and bill usage in batches:

import { db } from 'sdk.do'

// Track usage per customer
on($.ServiceResult.delivered, async (result) => {
  // Record usage
  await db.create($.UsageRecord, {
    customerId: result.customerId,
    serviceId: result.serviceId,
    units: result.usage.units,
    timestamp: new Date(),
    billed: false,
  })
})

// Aggregate and bill monthly
on($.BillingCycle.ended, async (cycle) => {
  const customers = await db.query($.Customer, { status: 'active' })

  for (const customer of customers) {
    // Get unbilled usage
    const usage = await db.query($.UsageRecord, {
      where: {
        customerId: customer.id,
        billed: false,
        timestamp: { gte: cycle.start, lte: cycle.end },
      },
    })

    // Calculate total
    const totalUnits = usage.reduce((sum, record) => sum + record.units, 0)
    const totalCost = totalUnits * service.pricing.rate

    if (totalCost > 0) {
      // Create invoice
      await send($.Invoice.create, {
        customerId: customer.id,
        amount: totalCost,
        period: { start: cycle.start, end: cycle.end },
        lineItems: [
          {
            description: `${service.name} Usage`,
            quantity: totalUnits,
            rate: service.pricing.rate,
            amount: totalCost,
          },
        ],
      })

      // Mark usage as billed
      await db.update($.UsageRecord, {
        where: { id: { in: usage.map((u) => u.id) } },
        data: { billed: true },
      })
    }
  }
})

Rate Limiting

Prevent excessive usage:

async function checkRateLimit(customerId: string, serviceId: string): Promise<boolean> {
  const now = new Date()
  const hourAgo = new Date(now.getTime() - 60 * 60 * 1000)

  // Count recent requests
  const recentUsage = await db.query($.UsageRecord, {
    where: {
      customerId,
      serviceId,
      timestamp: { gte: hourAgo },
    },
    count: true,
  })

  // Get customer's rate limit
  const customer = await db.findOne($.Customer, { id: customerId })
  const limit = customer.tier?.rateLimit || 100

  return recentUsage.count < limit
}

on($.ServiceRequest.created, async (request) => {
  // Check rate limit
  const allowed = await checkRateLimit(request.customerId, request.serviceId)

  if (!allowed) {
    return await send($.ServiceRequest.reject, {
      requestId: request.id,
      reason: 'Rate limit exceeded',
      retryAfter: 3600, // 1 hour in seconds
    })
  }

  // Process request...
})

Cost Estimation

Provide upfront cost estimates:

on($.ServiceEstimate.requested, async (estimateRequest) => {
  const service = await db.findOne($.Service, { id: estimateRequest.serviceId })
  const inputs = estimateRequest.inputs

  let estimate = 0

  switch (service.pricing.model) {
    case 'per-word':
      const wordCount = countWords(inputs.text)
      estimate = wordCount * service.pricing.rate
      break

    case 'per-image':
      const imageCount = inputs.imageCount || 1
      const quality = inputs.quality || 'standard'
      estimate = imageCount * service.pricing.tiers[quality].rate
      break

    case 'composite':
      // Calculate each component
      for (const [component, enabled] of Object.entries(inputs.components)) {
        if (enabled) {
          estimate += service.pricing.components[component].rate
        }
      }
      break
  }

  // Apply minimum charge if configured
  if (service.pricing.minimumCharge) {
    estimate = Math.max(estimate, service.pricing.minimumCharge)
  }

  await send($.ServiceEstimate.deliver, {
    estimateId: estimateRequest.id,
    estimate: {
      amount: estimate,
      currency: 'USD',
      breakdown: calculateBreakdown(service, inputs),
      confidence: 0.95, // Estimation confidence
    },
  })
})

Usage Analytics

Track and report usage patterns:

async function getUsageAnalytics(customerId: string, period: string) {
  const usage = await db.query($.UsageRecord, {
    where: {
      customerId,
      timestamp: { gte: getStartOfPeriod(period) },
    },
  })

  return {
    totalUnits: usage.reduce((sum, r) => sum + r.units, 0),
    totalCost: usage.reduce((sum, r) => sum + r.cost, 0),
    averageCostPerUnit: usage.reduce((sum, r) => sum + r.cost, 0) / usage.length,
    byService: groupBy(usage, 'serviceId'),
    byDay: groupBy(usage, (r) => formatDate(r.timestamp)),
    trend: calculateTrend(usage),
    forecast: forecastUsage(usage),
  }
}

// Send monthly usage reports
on($.BillingCycle.ended, async (cycle) => {
  const customers = await db.query($.Customer, { status: 'active' })

  for (const customer of customers) {
    const analytics = await getUsageAnalytics(customer.id, 'last-month')

    await send($.Email.send, {
      to: customer.email,
      template: 'usage-report',
      data: {
        customer,
        analytics,
        period: cycle,
      },
    })
  }
})

Best Practices

1. Transparent Pricing

Always show customers exactly what they'll pay:

// Show cost before execution
on($.ServiceRequest.created, async (request) => {
  const estimate = await calculateCost(request)

  await send($.Customer.notify, {
    customerId: request.customerId,
    type: 'cost-estimate',
    data: {
      service: request.service.name,
      estimate: estimate.amount,
      breakdown: estimate.breakdown,
    },
  })

  // Wait for approval if high cost
  if (estimate.amount > 100) {
    await send($.Approval.request, {
      customerId: request.customerId,
      requestId: request.id,
      amount: estimate.amount,
    })
  }
})

2. Fair Billing

Only charge for successful executions:

on($.ServiceRequest.fail, async (failure) => {
  // Don't charge for failures
  if (failure.paymentId) {
    await send($.Payment.refund, {
      paymentId: failure.paymentId,
      reason: 'Service execution failed',
      fullRefund: true,
    })
  }
})

3. Usage Alerts

Notify customers before limits:

on($.UsageRecord.created, async (usage) => {
  const customer = await db.findOne($.Customer, { id: usage.customerId })
  const monthlyUsage = await getMonthlyUsage(usage.customerId)
  const budget = customer.usageBudget || Infinity

  const percentUsed = monthlyUsage.totalCost / budget

  if (percentUsed >= 0.8 && percentUsed < 0.9) {
    await send($.Customer.notify, {
      customerId: usage.customerId,
      type: 'usage-warning',
      message: "You've used 80% of your monthly budget",
      data: { used: monthlyUsage.totalCost, budget },
    })
  } else if (percentUsed >= 0.9) {
    await send($.Customer.notify, {
      customerId: usage.customerId,
      type: 'usage-critical',
      message: "You've used 90% of your monthly budget",
      data: { used: monthlyUsage.totalCost, budget },
    })
  }
})

4. Optimize Costs

Help customers reduce usage costs:

async function suggestOptimizations(customerId: string) {
  const usage = await getUsageAnalytics(customerId, 'last-month')

  const suggestions = []

  // Suggest batch processing
  if (usage.averageRequestSize < 10) {
    suggestions.push({
      type: 'batching',
      message: 'Process multiple items together to save up to 30%',
      potentialSavings: usage.totalCost * 0.3,
    })
  }

  // Suggest caching
  if (usage.duplicateRequests > usage.totalRequests * 0.2) {
    suggestions.push({
      type: 'caching',
      message: 'Cache common requests to avoid duplicate charges',
      potentialSavings: usage.totalCost * 0.2,
    })
  }

  // Suggest tier upgrade
  if (usage.totalCost > 500) {
    suggestions.push({
      type: 'subscription',
      message: 'Switch to monthly plan to save up to 40%',
      potentialSavings: usage.totalCost * 0.4,
    })
  }

  return suggestions
}

Real-World Examples

Translation Service

// $0.02/word with volume discounts
const pricing = {
  baseRate: 0.02,
  tiers: [
    { min: 0, max: 10000, rate: 0.02 },
    { min: 10001, max: 50000, rate: 0.015 }, // 25% off
    { min: 50001, max: Infinity, rate: 0.01 }, // 50% off
  ],
}

// Example costs:
// 5,000 words = $100
// 25,000 words = $425 (25% savings)
// 100,000 words = $1,250 (50% savings)

API Service

// $0.001/API call with free tier
const pricing = {
  free: { limit: 1000 },
  paid: { rate: 0.001 },
}

// Example costs:
// 0-1,000 calls = Free
// 5,000 calls = $4 (after free 1,000)
// 1,000,000 calls = $999

Image Generation

// $2-8 per image based on quality
const pricing = {
  standard: 2.0,
  hd: 4.0,
  ultra: 8.0,
}

// Example costs:
// 10 standard images = $20
// 10 HD images = $40
// 10 ultra images = $80

Next Steps

Resources