Events API
Query usage events: what the charging log recorded for authorize, charge and release requests. This is not the billing record, which Get Customer Transactions returns.
List Events
GET /api/v1/events
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | int | Items per page |
offset | int | Items to skip |
customer_id | string | Filter to one customer by their id. Use this rather than the account id alone: an account id can be changed and reused by another customer, while the id names one customer for as long as it exists. When no history of the customer's account ids is on file, the read uses their current account id, and answers an empty list if they have none. A value that cannot be an id is a 422 |
customer_external_id | string | Filter by the account id you gave a customer, their external_id. On its own it returns everything charged under that id, including charges made while it belonged to a different customer. Send customer_id as well to filter to one customer |
service_code | string | Filter by service |
status | string | Filter by status — one of the tokens under Event Statuses |
session_id | string | Filter to one session: returns only the events that make up that session |
lock_id | string | Filter to one reservation: every event charged against that session identity |
interaction_id | string | Filter to the legs of one API call. A three-service charge is one interaction and three events, so this is a different axis from session_id, which spans a whole session. Only events recorded by the current charging path carry one. Each repeat of the call under the same idempotency_key adds a copy of every event the call recorded, marked by replay_of |
search | string | Free-text match across the customer name and account ID, the service name and code, and the item code. With a customer filter, only the service and item fields are searched |
from_date | string | Start date (ISO 8601) |
to_date | string | End date (ISO 8601) |
Response:
{
"events": [
{
"event_id": "tx_abc123",
"type": "charge",
"customer_external_id": "acme-42",
"customer_name": "Acme Corp",
"service_code": "AI-TOKENS",
"item_code": "GPT-4-TURBO",
"session_id": "cv-review-42",
"lock_id": "0123ABCD 456789EF 0123ABCD 456789EF",
"interaction_id": "ia_9f2c…",
"requested_units": 1500,
"granted_units": null,
"reservation_units": 2000,
"charged_quantity": 1500,
"charged_amount": "0.00450",
"currency": "USD",
"status": "charged",
"succeeded": true,
"partial": false,
"created_at": "2026-03-23T10:30:00Z"
}
],
"total": 15420,
"limit": 20,
"offset": 0,
"has_more": true
}
event_id is the identity of that one event, and it is the same value the Charging API's own
reply reports — one name for one value, so a POST /api/v1/charging result can be read back here
directly. Money is a decimal string, never a number: null means "no figure", which is not the
same as zero.
On a charge, reservation_units is what the reported usage was measured against: everything the
billing engine had granted the session, including what it had already written off as used. It is
null when there is none to name, and null on every event that is not a charge.
over_grant is true when the session has reported more units than reservation_units covered.
Every charge of the session is measured against the reservation, so a session that closes in
several charges is flagged from the charge that crosses the reservation onward. It is informational: the charge
still billed in full.
All list endpoints return the same pagination envelope: total (rows matching the query), limit/offset (the page you requested), and has_more (whether more rows exist beyond this page).
Get Single Event
GET /api/v1/events/{event_id}
Returns full event details including metadata and processing timestamps. Pass customer_external_id to
refuse the read unless the event was charged under that account id; an event belonging to someone else
answers exactly as one that never existed.
An event still awaiting a reply from the billing backend is returned as it stands, with
state: "in_flight" and the charge figures null. That means the result is unknown, never
that nothing was charged.
Event Statuses
One event has one status, whichever read you meet it in: the event list, a single event, or the members of a session.
| Status | Description |
|---|---|
charged | Successfully rated and billed |
in_flight | Sent to the billing backend, which has not answered yet — the result is not known |
authorized | Resources locked (session in progress) — see the reservation fields below |
released | The reservation was handed back without a final charge |
skipped | A repeat of a settle that had already been applied; nothing was charged again |
failed | Processing error |
Reservation fields on an authorized event
An authorization reserves units, it does not consume them, and the billing backend may reserve fewer than were asked for when the customer's funds fall short.
| Field | Meaning |
|---|---|
requested_units | Units the caller asked for. |
granted_units | Units actually reserved. Null for an uncapped grant, and also when nothing was granted: unlimited_grant separates the two. |
unlimited_grant | true when an uncapped quantity was granted. |
partial | true when fewer units were reserved than were asked for. A success, not a refusal. |
So an authorization that asked for 120 units against a balance covering 50 reports
requested_units: 120, granted_units: 50, partial: true. Consume at most granted_units, and
extend the reservation to reserve more.
Sessions
Charges that share a session_id (see the Charging API) can be read back as grouped sessions, each with per-session revenue, cost and margin. See the Events guide for the UI.
List sessions
GET /api/v1/sessions
Query Parameters: customer_id, customer_external_id, service_code, search, from_date, to_date, limit, offset. The two customer filters work as they do on List Events.
Returns one entry per session with total_charged, total_cost, margin, event_count,
lock_count, label, service_codes, the started_at/ended_at window and the counts of
events still awaiting a reply (in_flight_count) or failed (failed_count). A copy that a
repeated request writes is not counted again in either. A copy carries the repeat's time, so a
resend after a session ends moves its ended_at. The list uses the same pagination
envelope as List Events. lock_count counts the lock ids the events name rather
than live reservations, so a session that reserved nothing still reports a non-zero count.
totals carries the same money split per currency, as decimal strings. It is not a
restatement of total_charged: a session that billed in more than one currency reports
currency: null and no single total, because amounts in different currencies cannot be added, and
totals is where its money is still readable.
Get a single session
GET /api/v1/sessions/{session_id}
Returns the session summary, its member events, the reservations it opened (locks) and a
per-service rollup (services).
Each member event is the same object List Events returns for that event, with the same fields and the same values.
A session is identified by customer and session_id together, because a session_id is yours to
choose and may be reused. Pass customer_external_id to say which customer you mean. If you omit it
for a shared session_id, the read answers 409 and error.code is session_id_ambiguous. The
candidates' account ids are in error.details.customer_external_ids, most recently active first, so you can repeat
the request for the one you meant. A session holding more events than one read returns reports
its newest ones and sets truncated, whether or not you named the customer.
Each entry in locks is one reservation — its state (open, settled or released),
granted_units, consumed_units (the last report on its own), session_reported_units
(everything the session has reported across its charges, and the figure over_grant is measured
against), expires_at, authorize_count, whether the session rates incrementally, and lapsed
(still open past its expiry, computed on every read).
A session none of whose events opened a reservation reports an empty list. A standalone charge
names a lock id without opening one, and so does a refused authorization.
To fetch only the raw events of a session, use GET /api/v1/events?session_id={session_id}. To reach
one reservation when you know only its lock_id, use GET /api/v1/events?lock_id={lock_id}: the
events name the session they belong to, and that session's detail carries the reservation.