Release held usage
const url = 'http://localhost:8080/v1/usage-events/held/release';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"external_customer_id":"example","customer_id":"example"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8080/v1/usage-events/held/release \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "external_customer_id": "example", "customer_id": "example" }'Writes the held rows for one external customer ID as usage events
for a customer, each under its original idempotency key and time,
marked origin: backfill, in one transaction. A row that is already
a usage event under the same key is not written again. Releasing
twice writes nothing the second time.
Rows whose meter still does not exist, or whose time is after the
customer’s current time, stay held. One call releases at most 5,000
rows; more_to_release counts the rest, and another call takes
them. A released row counts toward
the billing period its time falls in only if that period’s invoice
has not been built yet; Hardcount does not move it onto a later
invoice. Writes an audit log entry. Requires usage write permission.
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
The external ID the rows were held under.
Release into this customer (an hc_cus_ id) instead of
the one whose external ID matches, for a user value
that had a typo.
Example generated
{ "external_customer_id": "example", "customer_id": "example"}Responses
Section titled “Responses”Release result
The counts are what Hardcount found when the release wrote the rows, as
for billing_status (see UsageBillingStatus). The invoice is the
final answer.
object
The customer the rows were ingested for.
Rows newly written as usage events.
Rows that were already usage events under the same idempotency key (LiteLLM resent the call after the customer existed), and LiteLLM cache-write rows whose call is already billed with the writes inside its input row. Marked released, not written again.
Rows for this external ID still held that a release cannot take yet: their meter does not exist yet, or their time is after the customer’s current time (a customer on a test clock).
Rows for this external ID still held only because one release takes at most 5,000 rows. Release again to take them.
Released rows in a billing period not invoiced yet that a price
of its plan takes. Rows Hardcount does not check count here too:
rows after the current period’s end, while the subscription
continues, and rows after a trial’s end while the subscription
still trials. Rows of a sum or count meter stamped before the end
of this period’s spend-threshold invoice are not counted here:
see pending_threshold_invoice and outside_open_period.
Released rows in a billing period not invoiced yet whose plan
prices the meter, but no price of that plan takes them (their
dimensions do not include any price’s dimension_match).
Released rows in a billing period not invoiced yet whose plan does not price the meter.
Released rows of a sum or count meter stamped before the end of this period’s spend-threshold invoice, while that invoice is not paid. The period’s invoice counts them only if that invoice is voided before the period closes. A written-off invoice can still be voided.
Released rows outside every subscription’s time (before it started, in a free trial, after it ended or after its scheduled cancel, or in another, ended subscription’s time), and rows in a period already invoiced. Also rows of a sum or count meter stamped before the end of this period’s paid spend-threshold invoice. Bill them with a one-off invoice if they should be charged.
Example generated
{ "customer_id": "example", "released": 1, "deduplicated": 1, "still_held": 1, "more_to_release": 1, "in_open_period": 1, "no_price": 1, "not_on_plan": 1, "pending_threshold_invoice": 1, "outside_open_period": 1}validation_error: no customer has that external ID yet and no customer_id was given, or customer_id was not found.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}