Skip to main content
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. Every hook returns isLoading: boolean and error: Error | null (see 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
metricId
string
required
The billable metric to meter. The query stays disabled until a non-empty metricId (and a customer) is present.
Returns
  • meter — the UsageMeter for this metric, or null while loading:
metricId
string
The metric identifier.
metricName
string
Human-readable metric name for display.
usage
number
Units consumed this period.
limit
number | null
The period allowance, or null when unlimited.
isUnlimited
boolean
true when the plan grants unlimited usage of this metric.
percentage
number
Server-computed percent consumed (0100). 0 when unlimited.
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
days
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.
Returns
  • seriesRecord<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.
  • hasDatatrue when the server returned at least one aggregate row in the window. Use it to switch between an empty state and a populated chart.
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
creditCurrencyId
string
required
The credit wallet (currency) to measure. The query stays disabled until this and a customer are present.
days
number
default:"30"
Window length in trailing UTC days. The series is zero-filled across the whole window.
Returns
  • seriesArray<{ 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).
  • avgPerDaynumber — mean consumed per day over the window (windowSum / days, including zero days). The honest steady-state rate, not a cherry-picked active-day average.
  • windowSumnumber — total consumed across the window.
  • peaknumber — highest single-day consumption in the window.
  • daysnumber — the window length echoed back.
  • hasBurnbooleantrue when windowSum > 0. Use it to switch between an idle-wallet state and the burn chart.
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.
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

Credits & wallets

Wallet balances and the ledger useCreditBurn aggregates.

Entitlements & gates

The entitlement limit useUsageMeter reads against.