# Bill usage with meters: send usage from your app

> Send usage from your app to Anytime Capital meters in safe, idempotent batches of 100, read a customer's usage back, and let it bill on their subscription.

Source: https://anytime-capital.com/docs/bill-usage-with-meters · Markdown: https://anytime-capital.com/docs/bill-usage-with-meters.md · All docs: https://anytime-capital.com/docs/llms-full.txt

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](https://anytime-capital.com/docs/usage-billing.md); every field is on [Meters & usage events](https://anytime-capital.com/docs/meters.md).

## 1. Make a meter

1. In **Subscriptions → Meters**, create one: a name for invoices, the `eventName` your code will send (say `api_call`), how events add up (`sum`, `count`, `max` or `last`), the unit price and any included units.
2. 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.
3. Create an API key with `subscriptions` write 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**

```javascript
// 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**

```python
# 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"))
```

> **Send it promptly:** A timestamp more than 5 minutes ahead, more than 400 days ago, or inside a period that has already been billed is refused. Send usage as it happens, and keep the reporter's queue short.

## 3. Show customers their usage

**Usage this month**

```javascript
// 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.

---

Anytime Capital is a fintech company, not a bank. Payment processing, banking and payout services are provided by Anytime Capital's licensed partners. Rates shown are for US card payments; international cards, currency conversion and disputes may carry additional fees shown in your dashboard before you accept a payment.
