Usage Reporting
When you resell AI or SaaS services, you need to know what you’re paying upstream providers versus what you’re charging users. Murai’s usage reporting lets you attach cost metadata to spends and aggregate it into reports.
Attaching metadata to spends
Pass a metadata string (JSON) as the fourth argument to spend:
await wallet.spend('user_123', 500, 'ai-req-001', { metadata: JSON.stringify({ cost: 0.05, model: 'gpt-4o' }),});The metadata is stored alongside the ledger entry and used by
getUsageReport to compute provider cost totals.
Metadata conventions
The metadata field is a JSON string representing an object. The following fields are recognized:
| Field | Type | Description |
|---|---|---|
cost | number | Provider cost for this operation (non-negative) |
model | string | Optional model or service identifier |
You can include any additional fields — only cost is used for aggregation.
Validation rules
Murai validates metadata before storing it:
| Rule | Error |
|---|---|
| Must be valid JSON | ValidationError |
| Must parse as an object (not array/string/number) | ValidationError |
cost must be a non-negative finite number | ValidationError |
| Total size must be under 4 KB | ValidationError |
// Valid{ "cost": 0.05 }{ "cost": 0.05, "model": "gpt-4o", "tokens": 1500 }{ "model": "claude-sonnet" } // cost is optional
// Invalid"just a string" // not an object{ "cost": -0.05 } // negative cost{ "cost": Infinity } // non-finite cost[1, 2, 3] // array, not objectGenerating usage reports
Use getUsageReport to aggregate a user’s activity over a time range:
const report = await wallet.getUsageReport('user_123', { from: new Date('2025-01-01'), to: new Date('2025-01-31'),});The returned UsageReport contains:
| Field | Type | Description |
|---|---|---|
totalCredits | number | Sum of all credit (top-up) amounts |
totalDebits | number | Sum of all debit (spend) amounts (positive) |
totalProviderCost | number | Sum of all cost values from metadata |
transactionCount | number | Number of transactions in the range |
Computing your margin
Your margin is the difference between what you charged (in token units) and what the upstream provider charged you (in currency units):
const margin = report.totalDebits - report.totalProviderCost;console.log(`Margin: ${margin}`);Date range filtering
Both from and to are inclusive boundaries on the transaction’s createdAt
timestamp. If omitted, the report covers all time:
// All timeconst allTime = await wallet.getUsageReport('user_123', {});
// Last 7 daysconst week = await wallet.getUsageReport('user_123', { from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), to: new Date(),});Full example
import { createDrizzleStorage, createWallet } from '@murai-wallet/murai';import { drizzle } from 'drizzle-orm/postgres-js';import postgres from 'postgres';
// biome-ignore lint/style/noNonNullAssertion: env vars validated at startupconst sql = postgres(process.env.DATABASE_URL!);const storage = createDrizzleStorage(drizzle(sql));const wallet = createWallet({ storage });
// Spend with provider cost metadataawait wallet.spend('user_123', 500, 'ai-req-001', { metadata: JSON.stringify({ cost: 0.05, model: 'gpt-4o' }),});
// Generate a usage report for the past 30 daysconst report = await wallet.getUsageReport('user_123', { from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), to: new Date(),});
// biome-ignore lint/suspicious/noConsole: example scriptconsole.log(`Credits: ${report.totalCredits}`);// biome-ignore lint/suspicious/noConsole: example scriptconsole.log(`Debits: ${report.totalDebits}`);// biome-ignore lint/suspicious/noConsole: example scriptconsole.log(`Provider cost: $${report.totalProviderCost.toFixed(4)}`);// biome-ignore lint/suspicious/noConsole: example scriptconsole.log(`Transactions: ${report.transactionCount}`);What’s next?
- Types reference — see
UsageReportandSpendOptions - Token Expiration — FIFO bucket-based expiration
- Architecture — append-only ledger and locking