Entitlements
Can this customer do this, right now?
An entitlement check answers one question: can this customer do this, right now? You call it before the work starts, not after.
The answer depends on the customer’s plan and their current credit balance. A check runs fast enough to call on every request.
Which call do I want?
- You want to know whether a customer can do something, and change nothing →
GET /v1/entitlements/checkA check never holds credits. The balance can still change before you call again. - You want to charge a customer for something they already did →
POST /v1/entitlements/consumeA check changes nothing. A consume takes credits. Calling check before consume is not required, and it does not reserve anything. The balance can change between the two calls. - You already let the action happen, and only want to bill for it afterward →
recordUsagerecordUsagequeues the event and bills it later, in the background.consumeEntitlementchecks and charges right away, and tells you the result at once. UseconsumeEntitlementto decide whether to allow the action. UserecordUsageonce you already have. - You want every entitlement for a customer, not one metric alone →
GET /v1/customers/{id}/entitlements - You are tracking a running count that has a cap, like seats or storage →
POST /v1/customers/{id}/entitlements/{billable_metric_key}/incrementA gauge has no price of its own. QuotaStack blocks or allows based on the cap, never the credit balance. UsedecrementGaugewhen a seat frees up or storage is deleted.
Check an entitlement
/v1/entitlements/check | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing to the entitlement itself. A check never touches a balance or a subscription. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | No webhooks. |
A check answers one question: can the customer do this, right now? QuotaStack changes nothing when you call it. Send units for a metered metric. QuotaStack ignores units for every other type. The four entitlement types answer with different fields — see the type table below.
| Field | Type | Meaning |
|---|---|---|
customer_idrequired | string | The customer’s own id, or your own |
billable_metric_keyrequired | string | Which billable metric to check. |
unitsoptional | integer | How many units to check for. Only a metered metric uses this — every other type ignores it. If you leave it out: Left out, QuotaStack checks for 1 unit. |
{
"type": "metered",
"allowed": true,
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 1,
"balance": 250000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 250000,
"estimated_cost": 500,
"balance_after": 249500,
"subscription_status": "active",
"overage_policy": "block"
} | Field | Type | Meaning |
|---|---|---|
typerequired | string | Which entitlement type this metric is.
|
allowedrequired | boolean | Whether QuotaStack says yes to this call, right now. |
customer_idrequired | string (uuid) | The customer’s own id inside QuotaStack. The same id comes back, no matter which id you sent. |
billable_metric_keyrequired | string | The billable metric key you asked about. |
unitsoptional | integer (int64) | How many units you asked about. Only present for a metered check. Unit: a count of billable metric units, not credits. |
balanceoptional | integer (int64) | What the customer has, before holds. Only present for a metered check. Unit: millicredits, where 1 credit is 1000 millicredits. |
reserved_balanceoptional | integer (int64) | Credits held by an open reservation. Only present for a metered check. Unit: millicredits. |
pending_balanceoptional | integer (int64) | Credits in a block that starts later. Only present for a metered check. Unit: millicredits. |
effective_balanceoptional | integer (int64) | What a new charge can spend, right now. Only present for a metered check. Unit: millicredits. |
estimated_costoptional | integer (int64) | What Unit: millicredits. |
balance_afteroptional | integer (int64) | How much would be left after this charge. Unit: millicredits. |
subscription_statusoptional | string | The customer’s subscription status. |
overage_policyoptional | string | The customer's own value wins when set. The tenant default overage rule applies when it is not. Only present for a metered check. |
enabledoptional | boolean | The flag’s value for this customer. Only present for a boolean check. |
configoptional | object | The free-form value your app should read. Comes from the customer’s plan variant, or the metric’s own default. Only present for a static check. |
currentoptional | integer (int64) | How many units this customer has already used, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
capoptional | integer (int64) | The most this customer may use, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
headroomoptional | integer (int64) | The cap minus how much the customer has used. Below zero once they are already over it. A plan downgrade can cause that, by lowering the cap. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
Errors
400— A required query parameter is missing:customer_idorbillable_metric_key. Aunitsvalue of zero or less returns this too. So does acustomer_idthat belongs to a different tenant. Use your owncustomer_id, or send your own external id instead. bad-request401— Missing or invalid authentication.404— No active metering rule covers thisbillable_metric_key. Aboolean,gauge, orstaticmetric never returns this — only a metered check can. not-found429— Rate limit exceeded.500— Unexpected server error.
Check an entitlement by customer ID and metric
/v1/customers/{id}/entitlements/{metric} | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing to the entitlement itself. A check never touches a balance or a subscription. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | No webhooks. |
checkCustomerEntitlement checks the same thing as checkEntitlement. The customer and the metric sit in the path here, not the query string. Both return the same result. openapi.yaml lists only 401 and 404 for this call. A bad units value still returns 400 in practice. So does a customer_id from another tenant.
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | The customer’s own id, or your own |
metricrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
unitsoptional | integer | How many units to check for. Only a metered metric uses this — every other type ignores it. If you leave it out: Left out, QuotaStack checks for 1 unit. |
{
"type": "metered",
"allowed": true,
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 1,
"balance": 250000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 250000,
"estimated_cost": 500,
"balance_after": 249500,
"subscription_status": "active",
"overage_policy": "block"
} | Field | Type | Meaning |
|---|---|---|
typerequired | string | Which entitlement type this metric is.
|
allowedrequired | boolean | Whether QuotaStack says yes to this call, right now. |
customer_idrequired | string (uuid) | The customer’s own id inside QuotaStack. The same id comes back, no matter which id you sent. |
billable_metric_keyrequired | string | The billable metric key you asked about. |
unitsoptional | integer (int64) | How many units you asked about. Only present for a metered check. Unit: a count of billable metric units, not credits. |
balanceoptional | integer (int64) | What the customer has, before holds. Only present for a metered check. Unit: millicredits, where 1 credit is 1000 millicredits. |
reserved_balanceoptional | integer (int64) | Credits held by an open reservation. Only present for a metered check. Unit: millicredits. |
pending_balanceoptional | integer (int64) | Credits in a block that starts later. Only present for a metered check. Unit: millicredits. |
effective_balanceoptional | integer (int64) | What a new charge can spend, right now. Only present for a metered check. Unit: millicredits. |
estimated_costoptional | integer (int64) | What Unit: millicredits. |
balance_afteroptional | integer (int64) | How much would be left after this charge. Unit: millicredits. |
subscription_statusoptional | string | The customer’s subscription status. |
overage_policyoptional | string | The customer's own value wins when set. The tenant default overage rule applies when it is not. Only present for a metered check. |
enabledoptional | boolean | The flag’s value for this customer. Only present for a boolean check. |
configoptional | object | The free-form value your app should read. Comes from the customer’s plan variant, or the metric’s own default. Only present for a static check. |
currentoptional | integer (int64) | How many units this customer has already used, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
capoptional | integer (int64) | The most this customer may use, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
headroomoptional | integer (int64) | The cap minus how much the customer has used. Below zero once they are already over it. A plan downgrade can cause that, by lowering the cap. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
Errors
401— Missing or invalid authentication.404— No active metering rule covers thismetric. Aboolean,gauge, orstaticmetric never returns this. not-found
See also
Get every entitlement for a customer
/v1/customers/{id}/entitlements | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing to the entitlement itself. A check never touches a balance or a subscription. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | No webhooks. |
This call returns one snapshot for one customer: the balance, the subscription, and every entitlement the tenant has defined. QuotaStack lists every billable metric the tenant has. A customer’s plan does not filter this list. allowed says whether this customer can use each one right now. QuotaStack caches this snapshot for up to 30 seconds. Send Cache-Control: no-cache to read a fresh one instead. The response also carries a pending_balance field, in millicredits. openapi.yaml does not yet declare it.
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | The customer’s own id, or your own |
{
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"environment": "live",
"balance": 250000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 250000,
"subscription_status": "active",
"plan_name": "Pro",
"entitlements": {
"chat_message": {
"type": "metered",
"billable_metric_key": "chat_message",
"allowed": true,
"estimated_cost_per_unit": 500,
"affordable_units": 500,
"cost_type": "per_unit"
},
"sso": {
"type": "boolean",
"billable_metric_key": "sso",
"allowed": true,
"enabled": true
},
"seats": {
"type": "gauge",
"billable_metric_key": "seats",
"allowed": true,
"current": 3,
"cap": 5,
"headroom": 2
}
},
"cached_at": "2026-07-27T10:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
customer_idrequired | string (uuid) | This customer’s id inside QuotaStack. The same id comes back, no matter which id you sent. |
environmentrequired | string | Sandbox or live. Matches the API key you called with.
|
balancerequired | integer (int64) | What the customer has, before holds. Unit: millicredits, where 1 credit is 1000 millicredits. |
reserved_balancerequired | integer (int64) | Credits held by an open reservation. Unit: millicredits. |
effective_balancerequired | integer (int64) | What a new charge can spend, right now. Unit: millicredits. |
subscription_statusoptional | string | The customer’s subscription status. |
plan_nameoptional | string | The customer’s plan name. |
entitlementsrequired | object | One entry per billable metric your tenant has, keyed by its |
cached_atrequired | string (date-time) | When QuotaStack computed this snapshot. The snapshot can be up to 30 seconds old. |
Errors
401— Missing or invalid authentication.404— Declared here, but never reached. An unrecognizedcustomer_idmakes a new customer, instead of a 404. Acustomer_idfrom another tenant gets400instead. not-found
Check an entitlement by external ID and metric
/v1/customer-by-external-id/{external_id}/entitlements/{metric} | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing to the entitlement itself. A check never touches a balance or a subscription. |
| Fires | No webhooks. |
This one works the same as checkCustomerEntitlement does. You use your own id here, not the QuotaStack one. A bad id here never makes a new customer — you get a 404 back instead. A bad units number still gives you a 400, in real use.
| 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. |
metricrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
unitsoptional | integer | How many units to check for. Only a metered metric uses this — every other type ignores it. If you leave it out: Left out, QuotaStack checks for 1 unit. |
{
"type": "metered",
"allowed": true,
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 1,
"balance": 250000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 250000,
"estimated_cost": 500,
"balance_after": 249500,
"subscription_status": "active",
"overage_policy": "block"
} | Field | Type | Meaning |
|---|---|---|
typerequired | string | Which entitlement type this metric is.
|
allowedrequired | boolean | Whether QuotaStack says yes to this call, right now. |
customer_idrequired | string (uuid) | The customer’s own id inside QuotaStack. The same id comes back, no matter which id you sent. |
billable_metric_keyrequired | string | The billable metric key you asked about. |
unitsoptional | integer (int64) | How many units you asked about. Only present for a metered check. Unit: a count of billable metric units, not credits. |
balanceoptional | integer (int64) | What the customer has, before holds. Only present for a metered check. Unit: millicredits, where 1 credit is 1000 millicredits. |
reserved_balanceoptional | integer (int64) | Credits held by an open reservation. Only present for a metered check. Unit: millicredits. |
pending_balanceoptional | integer (int64) | Credits in a block that starts later. Only present for a metered check. Unit: millicredits. |
effective_balanceoptional | integer (int64) | What a new charge can spend, right now. Only present for a metered check. Unit: millicredits. |
estimated_costoptional | integer (int64) | What Unit: millicredits. |
balance_afteroptional | integer (int64) | How much would be left after this charge. Unit: millicredits. |
subscription_statusoptional | string | The customer’s subscription status. |
overage_policyoptional | string | The customer's own value wins when set. The tenant default overage rule applies when it is not. Only present for a metered check. |
enabledoptional | boolean | The flag’s value for this customer. Only present for a boolean check. |
configoptional | object | The free-form value your app should read. Comes from the customer’s plan variant, or the metric’s own default. Only present for a static check. |
currentoptional | integer (int64) | How many units this customer has already used, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
capoptional | integer (int64) | The most this customer may use, for this gauge. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
headroomoptional | integer (int64) | The cap minus how much the customer has used. Below zero once they are already over it. A plan downgrade can cause that, by lowering the cap. Only present for a gauge check. Unit: a count of this gauge’s own units, not credits. |
Errors
401— Missing or invalid authentication.404— No customer here has thisexternal_id, or no active metering rule covers thismetric. not-found
See also
Get every entitlement by external ID
/v1/customer-by-external-id/{external_id}/entitlements | Idempotency-Key | Not used. This is a read. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Nothing to the entitlement itself. A check never touches a balance or a subscription. |
| Fires | No webhooks. |
getCustomerEntitlementsByExternalId is the external_id twin of getCustomerEntitlements. An unrecognized external_id never creates a customer here. QuotaStack returns 404 instead. Here, unlike the id form, 404 is real and reachable.
| 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. |
{
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"environment": "live",
"balance": 250000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 250000,
"subscription_status": "active",
"plan_name": "Pro",
"entitlements": {
"chat_message": {
"type": "metered",
"billable_metric_key": "chat_message",
"allowed": true,
"estimated_cost_per_unit": 500,
"affordable_units": 500,
"cost_type": "per_unit"
},
"sso": {
"type": "boolean",
"billable_metric_key": "sso",
"allowed": true,
"enabled": true
},
"seats": {
"type": "gauge",
"billable_metric_key": "seats",
"allowed": true,
"current": 3,
"cap": 5,
"headroom": 2
}
},
"cached_at": "2026-07-27T10:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
customer_idrequired | string (uuid) | This customer’s id inside QuotaStack. The same id comes back, no matter which id you sent. |
environmentrequired | string | Sandbox or live. Matches the API key you called with.
|
balancerequired | integer (int64) | What the customer has, before holds. Unit: millicredits, where 1 credit is 1000 millicredits. |
reserved_balancerequired | integer (int64) | Credits held by an open reservation. Unit: millicredits. |
effective_balancerequired | integer (int64) | What a new charge can spend, right now. Unit: millicredits. |
subscription_statusoptional | string | The customer’s subscription status. |
plan_nameoptional | string | The customer’s plan name. |
entitlementsrequired | object | One entry per billable metric your tenant has, keyed by its |
cached_atrequired | string (date-time) | When QuotaStack computed this snapshot. The snapshot can be up to 30 seconds old. |
Errors
401— Missing or invalid authentication.404— No customer here has thisexternal_id. not-found
See also
Increment a gauge
/v1/customers/{id}/entitlements/{billable_metric_key}/increment | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Adds amount to this customer’s gauge count for this metric. First use makes the counter at zero, then applies the increment. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | No webhooks. |
A gauge tracks a running count with a cap, like seats or storage. Increment adds amount to the count. The cap comes from the customer’s plan variant, or the metric’s own default. With policy: block, the default, an increment past the cap is refused. With policy: allow, QuotaStack lets it through instead — headroom in the response goes negative.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | Required idempotency key for this mutating admin/public operation. Reusing the same key with a different body returns |
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | The customer’s own id, or your own |
billable_metric_keyrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
amountrequired | integer (int64)1–∞ | How much to change the gauge by — added on increment, subtracted on decrement. Unit: a count of this gauge’s own units, not credits. If you leave it out: QuotaStack rejects the call. Must be a whole number above zero. |
policyoptional | string | What to do when the change would cross the cap. Only If you leave it out: Left out, QuotaStack uses
|
metadataoptional | object | Your own key-value tags. QuotaStack takes this field but throws it away — nothing is stored. If you leave it out: Nothing changes either way. The field has no effect. |
{
"amount": 1
} {
"billable_metric_key": "seats",
"current": 4,
"cap": 5,
"allowed": true,
"headroom": 1
} | Field | Type | Meaning |
|---|---|---|
billable_metric_keyrequired | string | The gauge metric this change touched. |
currentrequired | integer (int64) | The gauge’s count after this change. Unit: a count of this gauge’s own units, not credits. |
caprequired | integer (int64) | The cap in force right now. Comes from the customer’s plan variant, or the metric’s own default value. Unit: a count of this gauge’s own units, not credits. |
allowedrequired | boolean | Whether |
headroomrequired | integer (int64) |
Unit: a count of this gauge’s own units, not credits. |
Errors
400—amountis zero or negative.policycan also be wrong — anything other thanblockorallow. Or the metric is not agaugetype. So does acustomer_idthat belongs to a different tenant. Use your owncustomer_id, or send your own external id instead. bad-request401— Missing or invalid authentication.404— No billable metric here has this key at all. not-found409— Two different bodies come back here. Underpolicy: block, crossing the cap sends backcurrent,cap, andshortfall. A version clash sends back a plain conflict instead — retry it. conflict
Decrement a gauge
/v1/customers/{id}/entitlements/{billable_metric_key}/decrement | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Subtracts amount from this customer’s gauge count for this metric. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | No webhooks. |
Decrement subtracts amount from the customer’s gauge count. The count never goes below zero. QuotaStack refuses a decrement that would take it there. The request takes a policy field, the same one incrementGauge uses, but decrement ignores it.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | Required idempotency key for this mutating admin/public operation. Reusing the same key with a different body returns |
| Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | The customer’s own id, or your own |
billable_metric_keyrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
amountrequired | integer (int64)1–∞ | How much to change the gauge by — added on increment, subtracted on decrement. Unit: a count of this gauge’s own units, not credits. If you leave it out: QuotaStack rejects the call. Must be a whole number above zero. |
policyoptional | string | What to do when the change would cross the cap. Only If you leave it out: Left out, QuotaStack uses
|
metadataoptional | object | Your own key-value tags. QuotaStack takes this field but throws it away — nothing is stored. If you leave it out: Nothing changes either way. The field has no effect. |
{
"amount": 1
} {
"billable_metric_key": "seats",
"current": 2,
"cap": 5,
"allowed": true,
"headroom": 3
} | Field | Type | Meaning |
|---|---|---|
billable_metric_keyrequired | string | The gauge metric this change touched. |
currentrequired | integer (int64) | The gauge’s count after this change. Unit: a count of this gauge’s own units, not credits. |
caprequired | integer (int64) | The cap in force right now. Comes from the customer’s plan variant, or the metric’s own default value. Unit: a count of this gauge’s own units, not credits. |
allowedrequired | boolean | Whether |
headroomrequired | integer (int64) |
Unit: a count of this gauge’s own units, not credits. |
Errors
400—amountis zero or negative. The metric can also be the wrong type — not agauge. A customer id from a different tenant returns this too. bad-request401— Missing or invalid authentication.404— No billable metric here has this key at all. not-found409— The decrement would take the count below zero. Or another mutation on the same gauge ran at the same time. Retry either one. conflict
Check and charge in one call
/v1/entitlements/consume | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Writes one debit to the ledger, unless a reason value says nothing was charged. An unrecognized customer id is different. QuotaStack makes a new customer for it, the same as a credit grant. The new one starts with no balance and no subscription. |
| Fires | credit.consumed, credit.low_balance, credit.exhausted |
A consume checks whether the customer can afford units of a metered metric. QuotaStack charges them too, in the same step. Only a metered metric works here. Sending a boolean, gauge, or static metric’s key returns 400. An exact repeat of the same Idempotency-Key replays from cache for 24 hours, like every write. The Idempotency-Key cache can expire before you retry. reason: duplicate_replay catches that case too. QuotaStack replays the ORIGINAL numbers, not new ones.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | Required idempotency key for this mutating admin/public operation. Reusing the same key with a different body returns |
| Field | Type | Meaning |
|---|---|---|
customer_idrequired | string | The customer’s own id, or your own If you leave it out: QuotaStack rejects the call. This field is required. |
billable_metric_keyrequired | string | Which metered metric to charge for. This metric needs an active metering rule. If you leave it out: QuotaStack rejects the call. This field is required. |
unitsrequired | integer (int64)1–∞ | How many units this call used. Unit: a count of billable metric units, not credits. If you leave it out: QuotaStack rejects the call. Must be at least 1. |
metadataoptional | object | Your own key-value tags. QuotaStack writes them onto the ledger entry this call makes. If you leave it out: QuotaStack writes no tags onto the ledger entry. |
{
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10
} {
"type": "metered",
"allowed": true,
"debited": true,
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10,
"estimated_cost": 5000,
"balance": 245000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 245000,
"subscription_status": "active",
"overage_policy": "block"
} | Field | Type | Meaning |
|---|---|---|
typerequired | string | Always
|
allowedrequired | boolean | Whether QuotaStack says yes to this call, right now. |
debitedrequired | boolean | Whether QuotaStack took any credits at all. |
reasonoptional | string | Why the call ended this way. QuotaStack only sends this field when something notable happened. A normal call has no reason.
|
original_reasonoptional | string | The first call’s own
|
customer_idrequired | string (uuid) | The customer’s own id inside QuotaStack. The same id comes back, no matter which id you sent. |
billable_metric_keyrequired | string | The metric key this call charged against. |
unitsrequired | integer (int64) | The units this call charged for. On Unit: a count of billable metric units, not credits. |
estimated_costrequired | integer (int64) | What Unit: millicredits, where 1 credit is 1000 millicredits. |
balancerequired | integer (int64) | What the customer has, before holds, read fresh after this call. Unit: millicredits. |
reserved_balancerequired | integer (int64) | Credits held by an open reservation. A consume never changes this. Unit: millicredits. |
pending_balancerequired | integer (int64) | Credits in a block that starts later. Unit: millicredits. |
effective_balancerequired | integer (int64) | What a new charge can spend, right after this call. Unit: millicredits. |
shortfalloptional | integer (int64) |
Unit: millicredits. |
overageoptional | integer (int64) | Credit spent past the balance, logged for arrears billing later. Only present when Unit: millicredits. |
subscription_statusrequired | string | The customer’s subscription status. |
overage_policyrequired | string | The customer's own value wins when set. The tenant default overage rule applies when it is not. |
What comes back in each case
The customer had enough credit. QuotaStack went ahead and charged them.
{
"type": "metered",
"allowed": true,
"debited": true,
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10,
"estimated_cost": 5000,
"balance": 245000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 245000,
"subscription_status": "active",
"overage_policy": "block"
} QuotaStack charged nothing at all. Your app should stop right here.
{
"type": "metered",
"allowed": false,
"debited": false,
"reason": "insufficient_credits",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10,
"estimated_cost": 5000,
"balance": 2000,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 2000,
"shortfall": 3000,
"subscription_status": "active",
"overage_policy": "block"
} QuotaStack charged what the customer had, and logged the rest as overage. You bill for that part later.
{
"type": "metered",
"allowed": true,
"debited": true,
"reason": "overage_recorded",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10,
"estimated_cost": 5000,
"balance": 0,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 0,
"overage": 2000,
"subscription_status": "active",
"overage_policy": "allow"
} QuotaStack charged nothing the second time. The numbers you get back are from the first call.
{
"type": "metered",
"allowed": true,
"debited": true,
"reason": "duplicate_replay",
"original_reason": "overage_recorded",
"customer_id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a05",
"billable_metric_key": "chat_message",
"units": 10,
"estimated_cost": 5000,
"balance": 0,
"reserved_balance": 0,
"pending_balance": 0,
"effective_balance": 0,
"overage": 2000,
"subscription_status": "active",
"overage_policy": "allow"
} Errors
400—customer_id,billable_metric_key, or a positiveunitsvalue is missing. Aboolean,gauge, orstaticmetric also returns this. So does a customer id from a different tenant. bad-request401— Missing or invalid authentication.404— No active metering rule covers thisbillable_metric_key. not-found409— You reused thisIdempotency-Keywith a differentcustomer_id,billable_metric_key, orunits. The hash QuotaStack compares ignoresmetadata— changing only that still replays. idempotency-key-reuse422— TheIdempotency-Keyheader is missing here. QuotaStack’s idempotency middleware catches this before the handler runs. validation-error
See also
Increment a gauge by external ID
/v1/customer-by-external-id/{external_id}/entitlements/{billable_metric_key}/increment | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Adds amount to this customer’s gauge count for this metric. First use makes the counter at zero, then applies the increment. |
| Fires | No webhooks. |
incrementGaugeByExternalId works the same as incrementGauge. An unrecognized external_id never makes a customer here. 404 comes back instead.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | Required idempotency key for this mutating admin/public operation. Reusing the same key with a different body returns |
| 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. |
billable_metric_keyrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
amountrequired | integer (int64)1–∞ | How much to change the gauge by — added on increment, subtracted on decrement. Unit: a count of this gauge’s own units, not credits. If you leave it out: QuotaStack rejects the call. Must be a whole number above zero. |
policyoptional | string | What to do when the change would cross the cap. Only If you leave it out: Left out, QuotaStack uses
|
metadataoptional | object | Your own key-value tags. QuotaStack takes this field but throws it away — nothing is stored. If you leave it out: Nothing changes either way. The field has no effect. |
{
"amount": 1
} {
"billable_metric_key": "seats",
"current": 4,
"cap": 5,
"allowed": true,
"headroom": 1
} | Field | Type | Meaning |
|---|---|---|
billable_metric_keyrequired | string | The gauge metric this change touched. |
currentrequired | integer (int64) | The gauge’s count after this change. Unit: a count of this gauge’s own units, not credits. |
caprequired | integer (int64) | The cap in force right now. Comes from the customer’s plan variant, or the metric’s own default value. Unit: a count of this gauge’s own units, not credits. |
allowedrequired | boolean | Whether |
headroomrequired | integer (int64) |
Unit: a count of this gauge’s own units, not credits. |
Errors
400—amountis zero or negative.policycan also be wrong — anything other thanblockorallow. Or the metric is not agaugetype. bad-request401— Missing or invalid authentication.404— No customer here has thisexternal_id, or no billable metric has this key at all. not-found409— Two different bodies come back here. Underpolicy: block, crossing the cap sends backcurrent,cap, andshortfall. A version clash sends back a plain conflict instead — retry it. conflict
Decrement a gauge by external ID
/v1/customer-by-external-id/{external_id}/entitlements/{billable_metric_key}/decrement | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Subtracts amount from this customer’s gauge count for this metric. |
| Fires | No webhooks. |
decrementGaugeByExternalId works the same as decrementGauge. An unrecognized external_id never makes a customer here. 404 comes back instead.
| Field | Type | Meaning |
|---|---|---|
Idempotency-Keyrequired | string | Required idempotency key for this mutating admin/public operation. Reusing the same key with a different body returns |
| 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. |
billable_metric_keyrequired | string | The billable metric’s own key. Not a QuotaStack id. |
| Field | Type | Meaning |
|---|---|---|
amountrequired | integer (int64)1–∞ | How much to change the gauge by — added on increment, subtracted on decrement. Unit: a count of this gauge’s own units, not credits. If you leave it out: QuotaStack rejects the call. Must be a whole number above zero. |
policyoptional | string | What to do when the change would cross the cap. Only If you leave it out: Left out, QuotaStack uses
|
metadataoptional | object | Your own key-value tags. QuotaStack takes this field but throws it away — nothing is stored. If you leave it out: Nothing changes either way. The field has no effect. |
{
"amount": 1
} {
"billable_metric_key": "seats",
"current": 2,
"cap": 5,
"allowed": true,
"headroom": 3
} | Field | Type | Meaning |
|---|---|---|
billable_metric_keyrequired | string | The gauge metric this change touched. |
currentrequired | integer (int64) | The gauge’s count after this change. Unit: a count of this gauge’s own units, not credits. |
caprequired | integer (int64) | The cap in force right now. Comes from the customer’s plan variant, or the metric’s own default value. Unit: a count of this gauge’s own units, not credits. |
allowedrequired | boolean | Whether |
headroomrequired | integer (int64) |
Unit: a count of this gauge’s own units, not credits. |
Errors
400—amountis zero or negative, or the metric is not agaugetype. bad-request401— Missing or invalid authentication.404— No customer here has thisexternal_id, or no billable metric has this key at all. not-found409— The decrement would take the count below zero. Or another mutation on the same gauge ran at the same time. Retry either one. conflict
Loading…