> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useunitpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Usage & analytics

> Point-in-time usage meters, daily usage history, and credit burn-rate — for the customer in context.

import LearnHooksTip from '/snippets/learn-hooks-tip.mdx';

Hooks for the "X of Y used" reading of a single metric, the daily usage trajectory a point-in-time meter can't show, and how fast a credit wallet is being consumed. All three are scoped to the portal customer and read server-aggregated endpoints, so they stay correct at any event or ledger volume.

<LearnHooksTip />

Every hook returns `isLoading: boolean` and `error: Error | null` (see [Introduction](/react/introduction)); the sections below list each hook's specific returns.

## useUsageMeter

Reads a single metric's current-period usage against its entitlement limit — the live "X of Y used" reading that powers progress bars and quota warnings. It hits `GET /v1/usage?metricId=…` scoped to the portal customer.

**Parameters**

<ParamField body="metricId" type="string" required>
  The billable metric to meter. The query stays disabled until a non-empty `metricId` (and a customer) is present.
</ParamField>

**Returns**

* `meter` — the `UsageMeter` for this metric, or `null` while loading:

<ParamField body="metricId" type="string">The metric identifier.</ParamField>
<ParamField body="metricName" type="string">Human-readable metric name for display.</ParamField>
<ParamField body="usage" type="number">Units consumed this period.</ParamField>
<ParamField body="limit" type="number | null">The period allowance, or `null` when unlimited.</ParamField>
<ParamField body="isUnlimited" type="boolean">`true` when the plan grants unlimited usage of this metric.</ParamField>
<ParamField body="percentage" type="number">Server-computed percent consumed (`0`–`100`). `0` when unlimited.</ParamField>

```tsx theme={null}
import { useUsageMeter } from '@unitpay/react';

export default function ApiCallsMeter() {
  const { meter, isLoading, error } = useUsageMeter('api_calls');

  if (isLoading) return <div>Loading…</div>;
  if (error || !meter) return <div>Couldn’t load usage.</div>;

  if (meter.isUnlimited) {
    return <div>{meter.metricName}: {meter.usage.toLocaleString()} (unlimited)</div>;
  }

  return (
    <div>
      <div>{meter.metricName}: {meter.usage} / {meter.limit}</div>
      <progress value={meter.percentage} max={100} />
    </div>
  );
}
```

## useUsageHistory

Returns daily usage totals over a trailing window, one continuous series per event name. It reads the server-aggregated `GET /v1/usage/aggregate` endpoint (one row per UTC day × event, summed in Postgres) so it's correct at any event volume — a customer with a million events returns the same \~30 rows as one with ten. The hook returns the raw series only; charting is the caller's job.

**Parameters**

<ParamField body="days" type="number" default="30">
  Window length in trailing UTC days. The series is zero-filled across the whole window so charts render a continuous day axis even on quiet days.
</ParamField>

**Returns**

* `series` — `Record<eventName, Array<{ date, total }>>` — for each event name, a continuous daily series ordered oldest → today. Each point has `date` (UTC day, `YYYY-MM-DD`) and `total` (total quantity for that event on that day, `0` on days with no events). Day buckets are UTC-pinned on the server and the axis is built in UTC too, so they line up exactly.
* `hasData` — `true` when the server returned at least one aggregate row in the window. Use it to switch between an empty state and a populated chart.

```tsx theme={null}
import { useUsageHistory } from '@unitpay/react';

export default function UsageTable() {
  const { series, hasData, isLoading, error } = useUsageHistory(30);

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load usage history.</div>;
  if (!hasData) return <div>No usage in the last 30 days.</div>;

  return (
    <div>
      {Object.entries(series).map(([eventName, points]) => (
        <div key={eventName}>
          <h4>{eventName}</h4>
          <span>{points.reduce((sum, p) => sum + p.total, 0).toLocaleString()} total</span>
          {/* hand `points` to your chart library of choice */}
        </div>
      ))}
    </div>
  );
}
```

## useCreditBurn

Reports how fast one credit wallet is being consumed over a trailing window. It reads the server-aggregated `GET /v1/credits/stats` endpoint scoped to a single credit currency — the daily `consumed` series is summed in Postgres from the append-only ledger, so it's correct at any ledger volume.

**Parameters**

<ParamField body="creditCurrencyId" type="string" required>
  The credit wallet (currency) to measure. The query stays disabled until this and a customer are present.
</ParamField>

<ParamField body="days" type="number" default="30">
  Window length in trailing UTC days. The series is zero-filled across the whole window.
</ParamField>

**Returns**

* `series` — `Array<{ date, consumed }>` — zero-filled daily consumption ordered oldest → today. `date` is the UTC day (`YYYY-MM-DD`), `consumed` is credits consumed that day (`0` on quiet days).
* `avgPerDay` — `number` — mean consumed per day over the window (`windowSum / days`, **including zero days**). The honest steady-state rate, not a cherry-picked active-day average.
* `windowSum` — `number` — total consumed across the window.
* `peak` — `number` — highest single-day consumption in the window.
* `days` — `number` — the window length echoed back.
* `hasBurn` — `boolean` — `true` when `windowSum > 0`. Use it to switch between an idle-wallet state and the burn chart.

<Note>
  Runway is intentionally left to the caller: divide the wallet balance you already show on the card by `avgPerDay`, so the number matches the displayed balance exactly. Read the balance from [`useCreditAccounts`](/react/credits-and-wallets).
</Note>

```tsx theme={null}
import { useCreditAccounts, useCreditBurn } from '@unitpay/react';

export default function WalletBurn({ creditCurrencyId }: { creditCurrencyId: string }) {
  const { accounts } = useCreditAccounts();
  const { avgPerDay, windowSum, peak, hasBurn, isLoading, error } = useCreditBurn(creditCurrencyId, 30);

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Couldn’t load burn.</div>;
  if (!hasBurn) return <div>No credits consumed in the last 30 days.</div>;

  const balance = accounts.find((a) => a.creditCurrencyId === creditCurrencyId)?.balance ?? 0;
  const runwayDays = avgPerDay > 0 ? Math.floor(balance / avgPerDay) : Infinity;

  return (
    <div>
      <div>{windowSum.toLocaleString()} burned · {avgPerDay.toFixed(1)}/day · peak {peak}</div>
      <div>~{Number.isFinite(runwayDays) ? `${runwayDays} days left` : 'no burn'}</div>
    </div>
  );
}
```

## See also

<CardGroup cols={2}>
  <Card title="Credits & wallets" icon="coins" href="/react/credits-and-wallets">
    Wallet balances and the ledger `useCreditBurn` aggregates.
  </Card>

  <Card title="Entitlements & gates" icon="lock" href="/react/entitlements-and-gates">
    The entitlement limit `useUsageMeter` reads against.
  </Card>
</CardGroup>
