Skip to content

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:

FieldTypeDescription
costnumberProvider cost for this operation (non-negative)
modelstringOptional model or service identifier

You can include any additional fields — only cost is used for aggregation.

Validation rules

Murai validates metadata before storing it:

RuleError
Must be valid JSONValidationError
Must parse as an object (not array/string/number)ValidationError
cost must be a non-negative finite numberValidationError
Total size must be under 4 KBValidationError
// 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 object

Generating 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:

FieldTypeDescription
totalCreditsnumberSum of all credit (top-up) amounts
totalDebitsnumberSum of all debit (spend) amounts (positive)
totalProviderCostnumberSum of all cost values from metadata
transactionCountnumberNumber 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 time
const allTime = await wallet.getUsageReport('user_123', {});
// Last 7 days
const week = await wallet.getUsageReport('user_123', {
from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
to: new Date(),
});

Full example

lib/usage.ts
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 startup
const sql = postgres(process.env.DATABASE_URL!);
const storage = createDrizzleStorage(drizzle(sql));
const wallet = createWallet({ storage });
// Spend with provider cost metadata
await 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 days
const 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 script
console.log(`Credits: ${report.totalCredits}`);
// biome-ignore lint/suspicious/noConsole: example script
console.log(`Debits: ${report.totalDebits}`);
// biome-ignore lint/suspicious/noConsole: example script
console.log(`Provider cost: $${report.totalProviderCost.toFixed(4)}`);
// biome-ignore lint/suspicious/noConsole: example script
console.log(`Transactions: ${report.transactionCount}`);

What’s next?