SaaS Usage Metering & Verifiable Receipts
Track per-customer API usage with the Balance API and generate receipts your customers can independently verify.
This example shows the complete receipt-and-verification workflow: record usage as it happens, lock the period, generate a receipt, embed the proof root in your invoice, and give your customer a way to verify independently via GET /api/v1/verify/:proofRoot.
Prerequisites
- A PacSpace API key
- Node.js 18+ or Python 3.8+
- The PacSpace SDK (
npm install @pacspace-io/sdk) orrequestslibrary for Python
The Workflow
- Record usage — each API call your customer makes becomes a verified delta
- Lock the period — checkpoint at month-end to freeze the billing window
- Generate a proof — pull the receipt with the proof root and all deltas
- Build the invoice — embed the proof root and a verification link
- Customer verifies — your verification endpoint lets them independently confirm every charge
Step 1: Record API Usage
Every time a customer makes an API call, record it as a delta. The Balance API handles fingerprinting and permanent verification automatically.
TypeScript (SDK)
import { PacSpace } from '@pacspace-io/sdk';
const pac = new PacSpace({ apiKey: process.env.PACSPACE_API_KEY });
// Call this from your API middleware
async function recordApiCall(
customerId: string,
endpoint: string,
responseTimeMs: number,
) {
const result = await pac.balance.emit({
customerId,
delta: -1, // Each API call = 1 unit of usage
reason: `api_call:${endpoint}`,
referenceId: `${customerId}-${Date.now()}`,
});
console.log(`Recorded: ${customerId} → ${endpoint} (${result.anchorId})`);
return result;
}
// Express middleware example
app.use(async (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
if (req.customerId) {
recordApiCall(req.customerId, req.path, Date.now() - start);
}
});
next();
});
Python
import requests
from datetime import datetime, timezone
API_BASE = "https://balance-api.pacspace.io"
HEADERS = {
"X-Api-Key": "YOUR_API_KEY",
"Content-Type": "application/json",
}
def record_api_call(customer_id: str, endpoint: str, response_time_ms: int):
"""Record a single API call as a verified usage delta."""
payload = {
"customerId": customer_id,
"delta": -1,
"reason": f"api_call:{endpoint}",
"referenceId": f"{customer_id}-{datetime.now(timezone.utc).timestamp():.0f}",
}
response = requests.post(
f"{API_BASE}/api/v1/balance/delta", headers=HEADERS, json=payload
)
response.raise_for_status()
result = response.json()["data"]
print(f"Recorded: {customer_id} → {endpoint} ({result['anchorId']})")
return result
# Flask middleware example
@app.after_request
def track_usage(response):
if hasattr(request, "customer_id"):
record_api_call(
customer_id=request.customer_id,
endpoint=request.path,
response_time_ms=int(request.elapsed_ms),
)
return response
Step 2: Check Usage Mid-Period
At any time, derive the current balance to see how much usage has accumulated:
TypeScript (SDK)
const balance = await pac.balance.derive('cust_acme_001');
console.log(`Total usage: ${Math.abs(balance.computedBalance)} API calls`);
console.log(`Verified deltas: ${balance.deltasCount}`);
Python
def get_usage(customer_id: str):
"""Get the current usage total for a customer."""
response = requests.get(
f"{API_BASE}/api/v1/balance/derive/{customer_id}", headers=HEADERS
)
response.raise_for_status()
data = response.json()["data"]
return {
"customer_id": customer_id,
"total_calls": abs(data["computedBalance"]),
"verified_deltas": data["deltasCount"],
}
Step 3: Lock the Period
At month-end, checkpoint to freeze the period. This produces a proof root — a single fingerprint covering every verified delta.
TypeScript (SDK)
const checkpoint = await pac.balance.checkpoint('cust_acme_001', {
period: '2026-02',
});
// Store this — it goes on the invoice
const proofRoot = checkpoint.merkleRoot;
console.log(`Period locked. Proof root: ${proofRoot}`);
Python
def lock_period(customer_id: str, period: str):
"""Checkpoint the period."""
response = requests.post(
f"{API_BASE}/api/v1/balance/checkpoint",
headers=HEADERS,
json={"customerId": customer_id, "period": period},
)
response.raise_for_status()
data = response.json()["data"]
print(f"Period locked. Proof root: {data['merkleRoot']}")
return data
Step 4: Generate the Receipt
Pull the complete receipt for the period — all deltas, the proof root, and verification data.
TypeScript (SDK)
const checkpoint = await pac.balance.checkpoint('cust_acme_001', { period: '2026-02' });
const receipt = await pac.balance.receipt('cust_acme_001', { period: '2026-02' });
console.log(`Proof root: ${receipt.receiptId ?? receipt.verification?.itemsRoot ?? checkpoint.merkleRoot}`);
console.log(`Delta count: ${receipt.deltasCount}`);
console.log(`Final balance: ${receipt.finalBalance}`);
Python
def generate_receipt(customer_id: str, period: str):
"""Generate the receipt for a period."""
response = requests.get(
f"{API_BASE}/api/v1/balance/receipt/{customer_id}",
headers=HEADERS,
params={"period": period},
)
response.raise_for_status()
return response.json()["data"]
Step 5: Build the Invoice
Embed the proof root and a verification link in your invoice:
TypeScript (SDK)
async function generateInvoice(customerId: string, period: string, ratePerCall: number) {
const checkpoint = await pac.balance.checkpoint(customerId, { period });
const receipt = await pac.balance.receipt(customerId, { period });
const proofRoot = receipt.receiptId ?? receipt.verification?.itemsRoot ?? checkpoint.merkleRoot;
const invoice = {
invoiceId: `inv-${customerId}-${period}`,
customerId,
period,
totalApiCalls: receipt.deltasCount,
ratePerCall,
subtotal: receipt.deltasCount * ratePerCall,
proofRoot,
verifyUrl: `https://balance-api.pacspace.io/api/v1/verify/${proofRoot}`,
};
// Store in your database, send to customer, etc.
return invoice;
}
const invoice = await generateInvoice('cust_acme_001', '2026-02', 0.002);
Expected Output
============================================================
INVOICE: inv-cust_acme_001-2026-02
============================================================
Customer: cust_acme_001
Period: 2026-02
API Calls: 1,247
Rate: $0.0020 / call
Subtotal: $2.49
Proof root: 0x7f3a9b2c...
Verify at: https://balance-api.pacspace.io/api/v1/verify/0x7f3a9b2c...
============================================================
Step 6: Customer Verification
Give your customer a way to independently verify the invoice. Share the proof root or a direct link to the public verify endpoint — no API key or account needed.
Option A (recommended): Link directly to PacSpace's public verify endpoint:
https://balance-api.pacspace.io/api/v1/verify/:proofRoot
The proof root is the access key. No authentication required. See Verify — Proof Root & Compare.
Option B: If you need custom logic (e.g., combine with your invoice data), build your own endpoint:
import express from 'express';
import { PacSpace } from '@pacspace-io/sdk';
const app = express();
const pac = new PacSpace({ apiKey: process.env.PACSPACE_API_KEY });
// Optional: redirect to public verify or add custom logic
app.get('/verify/:proofRoot', (req, res) => {
res.redirect(`https://balance-api.pacspace.io/api/v1/verify/${req.params.proofRoot}`);
});
// Or derive for custom verification
app.get('/verify/custom/:customerId/:period', async (req, res) => {
const { customerId, period } = req.params;
const lastInvoice = await db.invoices.findPrevious(customerId, period);
const result = await pac.balance.derive(customerId, {
startingBalance: lastInvoice?.endingBalance ?? 0,
startingCheckpoint: lastInvoice?.proofRoot,
startingCheckpointType: 'itemsRoot',
});
const currentInvoice = await db.invoices.findByPeriod(customerId, period);
res.json({
period,
invoicedBalance: currentInvoice.endingBalance,
computedBalance: result.computedBalance,
verified: currentInvoice.endingBalance === result.computedBalance,
deltasVerified: result.deltasCount,
proofRoot: result.latestReceiptId,
});
});
// Dispute endpoint — when a customer disagrees
app.post('/dispute/:customerId/:period', async (req, res) => {
const { customerId, period } = req.params;
const { claimedBalance } = req.body;
const invoice = await db.invoices.findByPeriod(customerId, period);
const comparison = await pac.balance.compare({
customerId,
yourBalance: invoice.endingBalance,
theirBalance: claimedBalance,
});
res.json({
invoicedBalance: invoice.endingBalance,
claimedBalance,
verifiedBalance: comparison.computedBalance,
matchesInvoice: comparison.matchesYours,
matchesClaim: comparison.matchesTheirs,
resolution: comparison.discrepancy?.resolution,
});
});
Full Working Example
Here's the complete workflow from start to finish:
import { PacSpace } from '@pacspace-io/sdk';
const pac = new PacSpace({ apiKey: process.env.PACSPACE_API_KEY });
async function runBillingCycle() {
const customerId = 'cust_acme_001';
const period = '2026-02';
// 1. Record usage throughout the month
console.log('Recording API usage...\n');
await pac.balance.emit({ customerId, delta: -1, reason: 'api_call:/search' });
await pac.balance.emit({ customerId, delta: -1, reason: 'api_call:/search' });
await pac.balance.emit({ customerId, delta: -1, reason: 'api_call:/analytics' });
await pac.balance.emit({ customerId, delta: -1, reason: 'api_call:/export' });
await pac.balance.emit({ customerId, delta: -1, reason: 'api_call:/search' });
// 2. Check mid-period usage
const balance = await pac.balance.derive(customerId);
console.log(`Mid-period usage: ${Math.abs(balance.computedBalance)} calls\n`);
// 3. Lock the period
const checkpoint = await pac.balance.checkpoint(customerId, { period });
console.log(`Period locked. Proof root: ${checkpoint.merkleRoot}\n`);
// 4. Generate receipt
const receipt = await pac.balance.receipt(customerId, { period });
const proofRoot = receipt.receiptId ?? receipt.verification?.itemsRoot ?? checkpoint.merkleRoot;
console.log('============================================================');
console.log(` INVOICE: inv-${customerId}-${period}`);
console.log('============================================================');
console.log(` API Calls: ${receipt.deltasCount}`);
console.log(` Rate: $0.0020 / call`);
console.log(` Subtotal: $${(receipt.deltasCount * 0.002).toFixed(2)}`);
console.log(` Proof root: ${proofRoot}`);
console.log(` Verify at: https://balance-api.pacspace.io/api/v1/verify/${proofRoot}`);
console.log('============================================================');
}
runBillingCycle().catch(console.error);
Next Steps
- Receipts & Verification guide — detailed walkthrough of verification tiers and dispute handling
- Checkpoint Reference — full checkpoint endpoint documentation
- Customer Ledgers — how per-customer isolation works
- Webhook Setup — get real-time notifications when deltas are verified