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 = $999Image 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 = $80Next Steps
- Subscription Pricing → - Recurring revenue models
- Tiered Pricing → - Volume-based discounts
- Hybrid Models → - Combine pricing strategies
- Monetization Overview → - Compare all models
Resources
- Building Services → - Implement your pricing
- Best Practices → - Optimize your service
- Deployment → - Launch to production