Usage billing charges customers for what they use. You make a meter once in the dashboard; your app sends a usage event for each billable thing; each period, usage over the included units is invoiced and charged to the customer's saved card. How periods, rounding and invoices work is on Usage billing; every field is on Meters & usage events.
1. Make a meter
- In Subscriptions → Meters, create one: a name for invoices, the
eventNameyour code will send (sayapi_call), how events add up (sum,count,maxorlast), the unit price and any included units. - Attach it to a subscription plan to bill usage on top of the plan's price, or bill customers each calendar month without a plan.
- Create an API key with
subscriptionswrite for the reporter.
2. Send usage
Usage is matched to customers by email, so send customerEmail (or a customerId whose customer has an email). Give each event an idempotencyKey that's unique to the thing it counts, such as the request id: resending the same event never counts it twice, so the reporter can retry freely.
// usage.mjs — Node.js 18+. record() as things happen; batches go out every 10 seconds.
const API = "https://anytime-capital.com/api/v1";
const headers = {
Authorization: `Bearer ${process.env.ANYTIME_API_KEY}`, // subscriptions:write
"Content-Type": "application/json",
};
// In production, keep this in a table or durable queue so a restart loses nothing.
const queue = [];
let flushing = false;
/** One billable thing. `id` must be unique to it, such as your request id: it is the event's idempotencyKey. */
export function record({ eventName, customerEmail, value = 1, id }) {
queue.push({ eventName, customerEmail, value, idempotencyKey: id, timestamp: new Date().toISOString() });
}
/** Sends everything queued, 100 at a time. Safe to repeat: each idempotencyKey counts once. */
export async function flush() {
if (flushing) return;
flushing = true;
try {
while (queue.length > 0) {
const batch = queue.slice(0, 100);
let res;
try {
res = await fetch(`${API}/meter-events/batch`, { method: "POST", headers, body: JSON.stringify({ events: batch }) });
} catch {
return; // network: keep the queue, the next flush sends the same events again
}
if (res.status === 429 || res.status >= 500) return; // try again on the next tick, same events
const result = await res.json();
if (!res.ok) throw new Error(`${result.error.code}: ${result.error.message}`); // the batch's own shape
for (const outcome of result.data) {
if (outcome.status === "refused") {
// PERIOD_CLOSED, TIMESTAMP_TOO_OLD, METER_NOT_FOUND…: won't succeed on a retry. Log it and move on.
console.error("usage event refused", batch[outcome.index].idempotencyKey, outcome.error.code, outcome.error.message);
}
}
queue.splice(0, batch.length); // created and duplicate both mean it's counted
}
} finally {
flushing = false;
}
}
setInterval(() => flush().catch((error) => console.error(error)), 10_000);
// Example: bill one API call for the customer who made it.
record({ eventName: "api_call", customerEmail: "jordan@example.com", id: "req_7f3a9c" });# usage.py — pip install requests. One event at a time, safe to retry.
import os
import uuid
from datetime import datetime, timezone
import requests
API = "https://anytime-capital.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['ANYTIME_API_KEY']}"}
def record_usage(event_name, customer_email, value=1, key=None):
event = {
"eventName": event_name,
"customerEmail": customer_email,
"value": value,
"timestamp": datetime.now(timezone.utc).isoformat(),
"idempotencyKey": key or str(uuid.uuid4()), # pass your own request id to make retries safe
}
res = requests.post(f"{API}/meter-events", headers=HEADERS, json=event, timeout=30)
body = res.json()
if res.status_code in (200, 201): # 200: already recorded under this key
return body
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
print(record_usage("api_call", "jordan@example.com", key="req_7f3a9c"))3. Show customers their usage
// Show a customer their usage this month (subscriptions:read).
const params = new URLSearchParams({
customerEmail: "jordan@example.com",
from: new Date().toISOString().slice(0, 8) + "01", // the 1st of this month, UTC
});
const res = await fetch(`https://anytime-capital.com/api/v1/meters/mtr_8fKq2LmNpR4sT6vW9xYz/usage?${params}`, {
headers: { Authorization: `Bearer ${process.env.ANYTIME_API_KEY}` },
});
const usage = await res.json();
if (!res.ok) throw new Error(`${usage.error.code}: ${usage.error.message}`);
console.log(usage.quantity, usage.daily); // "1210", [{ date: "2026-10-01", quantity: "480" }, …]Windows run from from to to (now, by default), at most 366 days, with one point per UTC day. What a period will cost is on the subscriber's details in the dashboard.