> ## 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 & ingestion

> Track billable events one at a time, or batch them at high throughput.

Usage events feed both billing paths: they deduct credits at track-time and meter usage for invoice-time aggregation. Track a few events per request with `unitpay.usage.track`; for firehose volumes, use the stateful `UnitPayIngestion` batcher.

## unitpay.usage.track

Tracks one billable event, or an array of them in a single request. Also exposed as `unitpay.track(...)`.

**Parameters** — `TrackEventInput | TrackEventInput[]`:

<ParamField body="customerId" type="string" required>
  The customer the event belongs to.
</ParamField>

<ParamField body="eventName" type="string" required>
  The billable event, e.g. `ai-generation` or `api-call`.
</ParamField>

<ParamField body="quantity" type="number" default="1">
  How much usage to record.
</ParamField>

<ParamField body="properties" type="Record<string, unknown>">
  Arbitrary event metadata for aggregation and filtering.
</ParamField>

<ParamField body="timestamp" type="string">
  ISO-8601 event time. Defaults to now if omitted.
</ParamField>

<ParamField body="idempotencyKey" type="string">
  Dedupe key so retries don't double-count.
</ParamField>

**Returns** — `Promise<TrackEventResult>` with `accepted: number`, `rejected: number`, and optional `rejections` (`{ index, reason, message }[]`).

```ts theme={null}
import { UnitPay } from '@unitpay/node';

const unitpay = new UnitPay({ apiKey: process.env.UNITPAY_API_KEY });

// Single event
await unitpay.track({
  customerId: 'cus_123',
  eventName: 'ai-generation',
  quantity: 1,
  properties: { model: 'opus' },
});

// Batch — one request, one result
const result = await unitpay.usage.track([
  { customerId: 'cus_123', eventName: 'api-call', quantity: 10 },
  { customerId: 'cus_456', eventName: 'api-call', quantity: 3 },
]);
// result.accepted === 2
```

## UnitPayIngestion

A separate, stateful client for high-throughput pipelines. Events are buffered and auto-flushed on an interval or when the batch fills; retries and buffer overflow are handled for you. Construct it once and keep it alive for the process lifetime.

**Constructor** — `UnitPayIngestionConfig`:

<ParamField body="apiKey" type="string" required>
  Your secret key (`upay_sk_…`).
</ParamField>

<ParamField body="autoBatch" type="boolean" default="true">
  Flush on a timer. Set `false` to flush only manually.
</ParamField>

<ParamField body="flushIntervalMs" type="number" default="1000">
  How often the timer flushes the buffer.
</ParamField>

<ParamField body="maxBatchSize" type="number" default="100">
  Flush immediately once the buffer reaches this size.
</ParamField>

<ParamField body="maxBufferSize" type="number" default="10000">
  Hard cap; the oldest event is dropped on overflow (surfaced via `onFlushError`).
</ParamField>

<ParamField body="maxRetries" type="number" default="3">
  Retries on `5xx` / network errors before events go back in the buffer.
</ParamField>

<ParamField body="timeout" type="number" default="30000">
  Per-request timeout in ms.
</ParamField>

<ParamField body="onFlushError" type="(error, events) => void">
  Called when a flush fails or an event is dropped.
</ParamField>

<ParamField body="baseUrl" type="string" />

<ParamField body="fetch" type="typeof fetch" />

**Methods**

* `track(event)` — buffer one event (`TrackEventParams`); non-blocking. Fills in `quantity`, `timestamp`, and `idempotencyKey` if omitted.
* `flush()` — force-flush the buffer now. Returns `Promise<FlushResult>` (`{ accepted, rejected, rejections }`).
* `check(params)` — synchronous entitlement check, bypassing the buffer. Takes `{ customerId, featureSlug, requestedUsage? }`, returns `Promise<CheckResponse>` (`{ access: boolean, deniedReason? }`).
* `shutdown()` — stop the timer and flush what's left. Call on process exit.
* `bufferSize` — current number of buffered events.

```ts theme={null}
import { UnitPayIngestion } from '@unitpay/node';

const ingestion = new UnitPayIngestion({
  apiKey: process.env.UNITPAY_API_KEY!,
  onFlushError: (err, events) => console.error('flush failed', err, events.length),
});

// Fire-and-forget on the hot path
ingestion.track({ customerId: 'cus_123', eventName: 'api-call' });

// Gate an action before doing expensive work
const { access } = await ingestion.check({
  customerId: 'cus_123',
  featureSlug: 'ai-generation',
  requestedUsage: 1,
});
if (!access) throw new Error('over quota');

// Drain on shutdown
process.on('SIGTERM', () => ingestion.shutdown());
```

## See also

<CardGroup cols={2}>
  <Card title="Customers & subscriptions" icon="users" href="/node/customers">
    Read who you're billing.
  </Card>

  <Card title="Errors & pagination" icon="triangle-exclamation" href="/node/errors">
    Typed errors and retry behavior.
  </Card>
</CardGroup>
