Reservations
Hold credits while a long job runs.
A reservation holds credits before you know the real cost. You reserve an estimate, run the job, then settle the true number afterward.
Long AI jobs are why this pattern exists. Nobody knows how many tokens a job will burn until it finishes. A plain charge cannot wait that long.
Reserving locks estimated_cost inside reserved_balance. balance stays
untouched — the hold only blocks those same credits from being spent twice.
effective_balance drops by the same amount, since a new charge can never
spend a held credit.
Two ways to close a reservation. Commit it once you know the real cost.
QuotaStack charges actual_units, then returns whatever is left over. Release
it when the job fails, or costs nothing. QuotaStack returns the whole hold
and charges nothing.
A hold is not a charge. Commit charges the balance; release gives the hold back.
Nobody has to close a reservation by hand. Every reservation carries an
expires_at. QuotaStack’s scheduler sweeps for expired holds once a minute,
and releases them the same way a manual release would.
Already know the cost? Skip the hold. Call consumeEntitlement instead.
Which call do I want?
- You are about to start a long job and want the credits held →
POST /v1/reservations - You want to check on a hold before committing or releasing it →
GET /v1/reservations/{id} - The job finished and you know what it cost →
POST /v1/reservations/{id}/commit - The job failed, or cost nothing →
POST /v1/reservations/{id}/releaseA hold is not a charge. Credits stay in the balance the whole time it is open; the hold only stops them from being spent twice. Commit charges the balance. Release gives the hold back. Neither one is reversible. - You need every hold for one customer, not only the one you already have the ID for →
GET /v1/customers/{customer_id}/reservations
Reserve credits
/v1/reservations | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | One new reservation, and one zero-amount ledger entry, type reservation, for the audit trail. Raises reserved_balance by estimated_cost; balance is unchanged. |
| Fires | credit.low_balance, credit.exhausted |
This call holds credits before you know the exact cost of a job. Send customer_id or external_customer_id, never both. QuotaStack rejects the call with neither. An unrecognized value on either field creates a new customer, the same lookup grantCustomerCredits uses. QuotaStack prices estimated_units against the metric's active metering rule. QuotaStack holds that price in reserved_balance. balance itself stays the same. Leave out ttl_seconds and the hold stays open for 1800 seconds, 30 minutes. Send your own value, from 1 second up to 86400, 24 hours. A hold lowers effective_balance, the same as a real charge. So this call can trigger the low-balance and exhausted webhooks too, best-effort. This reply is not the full Reservation object the field table below describes. The real reply adds a nested account balance and drops actual_units, actual_cost, reference_id, committed_at, and released_at. openapi.yaml declares 404 and 402 here. Neither one is real. An unrecognized customer ID never returns 404 — QuotaStack makes the customer instead. A billable_metric_key with no active metering rule returns 500, not 404. A real insufficient-credit failure returns 409, type insufficient-credits, never 402.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example |
| Field | Type | Meaning |
|---|---|---|
customer_idoptional | string | QuotaStack's own ID for this customer, or your own lenient ID. Send this or If you leave it out: Send |
external_customer_idoptional | string | Your own ID for this customer. Looked up, or created, strictly by this value — no UUID fallback. If you leave it out: Send |
billable_metric_keyrequired | string | Which billable metric to price the hold against. Needs an active metering rule. If you leave it out: QuotaStack rejects the call. This field is required. |
estimated_unitsrequired | integer (int64)1–∞ | How many units you expect the job to use. Unit: a count of billable metric units, not credits. If you leave it out: QuotaStack rejects the call. Must be at least 1. |
ttl_secondsoptional | integer1–86400 | How long the hold stays open before it lapses on its own. Unit: seconds. If you leave it out: The hold stays open for 1800 seconds — 30 minutes — by default. |
metadataoptional | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
{
"external_customer_id": "user_42",
"billable_metric_key": "chat_message",
"estimated_units": 500,
"ttl_seconds": 1800
} {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"estimated_units": 500,
"estimated_cost": 250000,
"environment": "live",
"status": "active",
"expires_at": "2026-07-27T10:30:00Z",
"account": {
"balance": 500000,
"reserved_balance": 250000,
"effective_balance": 250000
},
"metadata": {},
"created_at": "2026-07-27T10:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
customer_idrequired | string (uuid) | |
environmentrequired | string |
Which environment this resource lives in. Determined by the API key prefix used ( |
billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
statusrequired | string | The hold's current state, right now.
|
reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
created_atrequired | string (date-time) | |
committed_atoptional | string (date-time) | When you committed it. |
released_atoptional | string (date-time) | When the hold was given back. |
Errors
400— Sending bothcustomer_idandexternal_customer_id, or neither, returns this. So does acustomer_idUUID that belongs to a different tenant. bad-request402— openapi.yaml lists this status here. QuotaStack never sends it. A real failure returns409instead, typeinsufficient-credits. This call's own spec entry never lists409at all. insufficient-credits404— openapi.yaml lists this status. Nothing here returns it. An unrecognizedcustomer_idmakes a new customer instead. So does an unrecognizedexternal_customer_id. Abillable_metric_keyneeds a priced metering rule. Without one, this call returns500instead. not-found
Retrieve a reservation
/v1/reservations/{id} | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing. A read has no side effects. |
| Fires | No webhooks. |
This call returns one reservation by its own id. Check whether a hold is still active before committing or releasing it, or use this to debug one that seems stuck. This lookup is not scoped by environment. A sandbox key can read a hold created under a live key for the same tenant, and the reverse. That gap also applies to commitReservation and releaseReservation, both of which fetch the same way.
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) |
{
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"environment": "live",
"billable_metric_key": "chat_message",
"estimated_units": 500,
"estimated_cost": 250000,
"actual_units": null,
"actual_cost": null,
"status": "active",
"expires_at": "2026-07-27T10:30:00Z",
"metadata": {},
"created_at": "2026-07-27T10:00:00Z",
"committed_at": null,
"released_at": null
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
customer_idrequired | string (uuid) | |
environmentrequired | string |
Which environment this resource lives in. Determined by the API key prefix used ( |
billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
statusrequired | string | The hold's current state, right now.
|
reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
created_atrequired | string (date-time) | |
committed_atoptional | string (date-time) | When you committed it. |
released_atoptional | string (date-time) | When the hold was given back. |
Errors
404— No reservation here has thisid. not-found
See also
Commit a reservation
/v1/reservations/{id}/commit | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Debits actual_cost from the balance and releases the whole hold, in one step. Writes one ledger entry, type consumption, for the debit. Fires credit.consumed, plus the low-balance and exhausted alerts — the same as a usage debit. actual_units: 0 skips all of that. No debit happens, and credit.consumed never fires. QuotaStack writes a zero-amount release entry instead. |
| Fires | credit.consumed, credit.low_balance, credit.exhausted |
Commit closes an active reservation once you know the real cost. QuotaStack prices actual_units at today's rate. That rate can differ from the one used to estimate the hold. QuotaStack charges that amount, then returns the rest. Using less than you reserved needs no separate release call. The leftover comes back in this same call. Using more than the hold covers, QuotaStack also tries your free balance for the difference. The call still succeeds if the hold plus your free balance covers actual_units. In that case, actual_cost in the response is still the full computed number. That can read higher than what QuotaStack actually collected. Send actual_units: 0 for a job that made nothing. QuotaStack returns the whole hold and charges nothing, the same as releaseReservation. But status reads committed here, not released. A negative actual_units returns 422, a status this call does not declare. This reply is not the full Reservation object the field table below describes. The real reply uses reservation_id, not id. The real reply drops most of the other fields too. Send a key unique to this hold, for example commit:{id} — the idempotency page's own convention. QuotaStack scopes a key by your tenant and its value alone, never by the reservation in the path. Reuse a key across two holds with the same body. The second call replays the first instead of committing it.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example |
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) |
| Field | Type | Meaning |
|---|---|---|
actual_unitsrequired | integer (int64)0–∞ | How many units the job really used. Unit: a count of billable metric units, not credits. If you leave it out: QuotaStack rejects the call. This field is required; 0 is a valid value and releases the whole hold. |
metadataoptional | object | Your own key-value tags. QuotaStack merges them into this hold's stored tags. If you leave it out: Nothing changes. QuotaStack also discards |
{
"actual_units": 420
} {
"reservation_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"status": "committed",
"estimated_units": 500,
"actual_units": 420,
"estimated_cost": 250000,
"actual_cost": 210000,
"released": 40000,
"transaction": {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"delta": -210000,
"type": "consumption",
"billable_metric_key": "chat_message",
"cost_units": 420,
"created_at": "2026-07-27T10:05:00Z"
},
"account": {
"balance": 290000,
"reserved_balance": 0,
"effective_balance": 290000
}
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
customer_idrequired | string (uuid) | |
environmentrequired | string |
Which environment this resource lives in. Determined by the API key prefix used ( |
billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
statusrequired | string | The hold's current state, right now.
|
reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
created_atrequired | string (date-time) | |
committed_atoptional | string (date-time) | When you committed it. |
released_atoptional | string (date-time) | When the hold was given back. |
What comes back in each case
The common case for an AI job. QuotaStack charges the real cost and returns the difference.
{
"reservation_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"status": "committed",
"estimated_units": 500,
"actual_units": 420,
"estimated_cost": 250000,
"actual_cost": 210000,
"released": 40000,
"transaction": {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"delta": -210000,
"type": "consumption",
"billable_metric_key": "chat_message",
"cost_units": 420,
"created_at": "2026-07-27T10:05:00Z"
},
"account": {
"balance": 290000,
"reserved_balance": 0,
"effective_balance": 290000
}
} Nothing comes back. released reads 0.
{
"reservation_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"status": "committed",
"estimated_units": 500,
"actual_units": 500,
"estimated_cost": 250000,
"actual_cost": 250000,
"released": 0,
"transaction": {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"delta": -250000,
"type": "consumption",
"billable_metric_key": "chat_message",
"cost_units": 500,
"created_at": "2026-07-27T10:05:00Z"
},
"account": {
"balance": 250000,
"reserved_balance": 0,
"effective_balance": 250000
}
} actual_units: 0 returns the whole hold and charges nothing. status reads committed, not released.
{
"reservation_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"status": "committed",
"estimated_units": 500,
"actual_units": 0,
"estimated_cost": 250000,
"actual_cost": 0,
"released": 250000,
"transaction": {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
"delta": 0,
"type": "release",
"reference_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"created_at": "2026-07-27T10:05:00Z"
},
"account": {
"balance": 500000,
"reserved_balance": 0,
"effective_balance": 500000
}
} Errors
402— openapi.yaml lists this status here. QuotaStack never sends it. A real failure returns409instead. See that row below. insufficient-credits404— No reservation here has thisid. not-found409— QuotaStack returns this for three reasons. Committing afterexpires_at, before the scheduler's sweep catches up, isreservation-expired. That window lasts about a minute. Once the sweep runs,statusalready readsexpired. Committing then returns the plainerconflictcause instead — same root problem, less specific. The hold leavingactivesome other way isconflicttoo — already committed, released, or expired. Reusing anIdempotency-Keywhose stored request matches this one exactly isconflictas well. QuotaStack replays the first response instead of failing. The hold plus your free balance falling short ofactual_costisinsufficient-credits, the third reason. The reservation stays active in every case here. reservation-expired
See also
Release a reservation
/v1/reservations/{id}/release | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Returns the whole hold to the balance and writes one zero-amount ledger entry, type release, for the audit trail. Charges nothing and fires no webhook. |
| Fires | No webhooks. |
Release closes an active reservation without charging anything. QuotaStack returns the whole estimated_cost to the balance. This call takes no request body. Release never checks expires_at. Releasing a hold that already expired, but is not yet swept, still succeeds. That produces the same result the sweep would have. This call always sends an empty body, so every release request hashes the same. Send a key unique to this hold, for example release:{id}. Reuse a key across two reservations, and the second call always replays the first. QuotaStack never returns a conflict here, since the body never changes.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | A unique string you choose, up to 256 characters. It stops a retry from doing the work twice. This header is required on every POST and PATCH. Leave it out and the request fails with Send the same key twice and QuotaStack replays the first response. The second call's status and body match the first, and the response carries Derive the key from the business event rather than a random value, so a retry from anywhere reuses it — for example |
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) |
{
"reservation_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"status": "released",
"estimated_cost": 250000,
"released": 250000,
"transaction": {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a32",
"delta": 0,
"type": "release",
"reference_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"created_at": "2026-07-27T10:10:00Z"
},
"account": {
"balance": 500000,
"reserved_balance": 0,
"effective_balance": 500000
}
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
customer_idrequired | string (uuid) | |
environmentrequired | string |
Which environment this resource lives in. Determined by the API key prefix used ( |
billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
statusrequired | string | The hold's current state, right now.
|
reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
created_atrequired | string (date-time) | |
committed_atoptional | string (date-time) | When you committed it. |
released_atoptional | string (date-time) | When the hold was given back. |
Errors
404— No reservation here has thisid. not-found409— QuotaStack returns this for two reasons. The hold already leftactive, either committed, released, or expired. Or you sent anIdempotency-Keythat matches an old request. QuotaStack replays the old reply instead of failing. conflict
See also
List a customer's reservations
/v1/customers/{customer_id}/reservations | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing. A read has no side effects. |
| Fires | No webhooks. |
This call lists every reservation for one customer, newest first. Filter by status. Leave it out for every status. This lookup is lenient. customer_id takes your own external_id or QuotaStack's own id. This lookup never makes a new customer. An unrecognized customer_id returns 404 here. openapi.yaml does not declare that; only 200 is listed. An invalid cursor returns 400, also undeclared. An unrecognized status value returns 500, also undeclared. status binds to a raw database column. QuotaStack never checks it first. This call returns the real Reservation shape. reserveCredits, commitReservation, and releaseReservation do not.
| Field | Type | Meaning |
|---|---|---|
customer_idrequired | string | Customer identifier. Can be either a QuotaStack UUID or your own |
| Field | Type | Meaning |
|---|---|---|
statusoptional | string | Only holds in this state. If you leave it out: Every status comes back, with no filter.
|
cursoroptional | string | Opaque pagination cursor from the previous page's |
limitoptional | integer | Page size. Default 20, max 100. |
{
"data": [
{
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"environment": "live",
"billable_metric_key": "chat_message",
"estimated_units": 500,
"estimated_cost": 250000,
"actual_units": null,
"actual_cost": null,
"status": "active",
"expires_at": "2026-07-27T10:30:00Z",
"metadata": {},
"created_at": "2026-07-27T10:00:00Z",
"committed_at": null,
"released_at": null
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
} | Field | Type | Meaning |
|---|---|---|
datarequired | array | |
data.billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
data.estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
data.estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
data.actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
data.actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
data.statusrequired | string | The hold's current state, right now.
|
data.reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
data.expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
data.metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
data.committed_atoptional | string (date-time) | When you committed it. |
data.released_atoptional | string (date-time) | When the hold was given back. |
paginationrequired | object | Cursor-based pagination envelope. Returned on every list endpoint. No |
See also
List a customer's reservations by external ID
/v1/customer-by-external-id/{external_id}/reservations | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing. A read has no side effects. |
| Fires | No webhooks. |
This call is the external_id twin of listCustomerReservations. Only the lookup differs. This one is strict, with no auto-create. An unrecognized external_id returns a real 404 here. openapi.yaml does not declare that either; only 200 is listed. An unrecognized status filter value still returns 500, the same undeclared gap the id form has.
| Field | Type | Meaning |
|---|---|---|
external_idrequired | string | Your own ID for this customer, set when you created it. QuotaStack looks up only this value, never the QuotaStack UUID. If you leave it out: QuotaStack rejects the call. This path segment is required. |
| Field | Type | Meaning |
|---|---|---|
cursoroptional | string | Opaque pagination cursor from the previous page's |
limitoptional | integer | Page size. Default 20, max 100. |
{
"data": [
{
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a41",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"environment": "live",
"billable_metric_key": "chat_message",
"estimated_units": 500,
"estimated_cost": 250000,
"actual_units": null,
"actual_cost": null,
"status": "active",
"expires_at": "2026-07-27T10:30:00Z",
"metadata": {},
"created_at": "2026-07-27T10:00:00Z",
"committed_at": null,
"released_at": null
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
} | Field | Type | Meaning |
|---|---|---|
datarequired | array | |
data.billable_metric_keyrequired | string | Which billable metric this hold is priced against. |
data.estimated_unitsrequired | integer (int64) | How many units you expect the job to use. QuotaStack prices the hold from this number. Unit: a count of billable metric units, not credits. |
data.estimated_costrequired | integer (int64) | What the hold costs, at today's price for Unit: millicredits, where 1 credit is 1000 millicredits. |
data.actual_unitsoptional | integer (int64) | How many units the job really used. Unit: a count of billable metric units, not credits. |
data.actual_costoptional | integer (int64) | What QuotaStack charged for Unit: millicredits. |
data.statusrequired | string | The hold's current state, right now.
|
data.reference_idrequired | string (uuid) | QuotaStack's own ID for this hold's ledger entries. The hold, the commit, and any release all share it. |
data.expires_atrequired | string (date-time) | When the hold lapses on its own, if nobody commits or releases it first. Set once, from |
data.metadatarequired | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: An empty object is stored instead. |
data.committed_atoptional | string (date-time) | When you committed it. |
data.released_atoptional | string (date-time) | When the hold was given back. |
paginationrequired | object | Cursor-based pagination envelope. Returned on every list endpoint. No |
See also
Loading…