Billable metrics
The named things you charge for.
A billable metric is the named thing you charge for. One chat message, one API call, one gigabyte of storage — each is a metric with its own key.
A metric alone has no price. A metering rule sets the price for one unit, and a usage event reports that a customer used it.
Which call do I want?
- You are defining a new thing to charge for or gate →
POST /v1/billable-metrics - You already know the key and want its definition →
GET /v1/billable-metrics/{key} - You want every billable metric you have defined →
GET /v1/billable-metricsA metering rule can make a plain billable metric on its own. List here to see everything, not only what you made by hand.
List billable metrics
/v1/billable-metrics | 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 page lists billable metrics, oldest first.
| 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-4f6c8e1b3a52",
"key": "chat_message",
"name": "Chat message",
"description": "One message sent in a chat session.",
"status": "active",
"type": "metered",
"default_value": {},
"metadata": {},
"created_at": "2026-07-20T09:00:00Z",
"updated_at": "2026-07-20T09:00:00Z"
}
],
"pagination": {
"has_more": false,
"next_cursor": null
}
} | Field | Type | Meaning |
|---|---|---|
datarequired | array | One row per billable metric, oldest first. |
paginationrequired | object | Cursor-based pagination envelope. Returned on every list endpoint. No |
See also
Create a billable metric
/v1/billable-metrics | Idempotency-Key | Not required for this operation. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | One new billable metric. status is active right away. |
| Fires | No webhooks. |
Creating a billable metric names one thing your app meters or gates. Creating one needs an Idempotency-Key header, like every write. openapi.yaml skips it here, but QuotaStack still checks. The metric alone sets no price. Add a metering rule for that.
| Field | Type | Meaning |
|---|---|---|
keyrequired | string | The name your code sends on every usage event and metering rule for this metric, like If you leave it out: QuotaStack rejects the call. This field is required. |
namerequired | string | A label for lists and dashboards. If you leave it out: QuotaStack rejects the call. This field is required. |
descriptionoptional | string | Longer, free text for dashboards. If you leave it out: Left out, QuotaStack stores an empty string. |
typeoptional | string | What kind of entitlement this metric is, and what a check call returns for it. If you leave it out: Left out, this metric is
|
default_valueoptional | object | The starting value for this metric’s If you leave it out: Left out, QuotaStack stores |
metadataoptional | object | Your own key-value tags. QuotaStack stores them unchanged and returns them on every read. If you leave it out: Left out, QuotaStack stores |
{
"key": "chat_message",
"name": "Chat message",
"description": "One message sent in a chat session.",
"type": "metered"
} {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a52",
"key": "chat_message",
"name": "Chat message",
"description": "One message sent in a chat session.",
"status": "active",
"type": "metered",
"default_value": {},
"metadata": {},
"created_at": "2026-07-20T09:00:00Z",
"updated_at": "2026-07-20T09:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
keyrequired | string | The name your code sends with each use of this metric, and with its metering rule. |
namerequired | string | |
descriptionrequired | string | Longer, free text for dashboards. An empty string unless you set one. |
statusrequired | string | Active or inactive, for your own records. QuotaStack does not use this to block usage today.
|
typerequired | string | What kind of entitlement this metric is, and what a check call returns for it. Fixed after creation.
|
default_valuerequired | object | The starting value for this metric’s If you leave it out: Left out, QuotaStack stores |
metadatarequired | object | Your own key-value tags, unchanged since the last write. |
created_atrequired | string (date-time) | |
updated_atrequired | string (date-time) |
Errors
409— Thiskeyalready exists for your tenant. A metering rule made earlier for this key can cause this too. Creating a rule for an unknown key makes a plain billable metric on its own. CallgetBillableMetricto see what is already there. conflict422—keyornameis missing,typeis not one of the four values, ordefault_valuedoes not matchtype’s shape. A missingIdempotency-Keyheader returns this too. The response names the field where it can. validation-error
Retrieve a billable metric
/v1/billable-metrics/{key} | 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 endpoint returns one billable metric by its key. The metering rule that prices it is separate — list those, filtered by this same key.
| Field | Type | Meaning |
|---|---|---|
keyrequired | string | The billable metric’s own key, exactly as you sent it to |
{
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a52",
"key": "chat_message",
"name": "Chat message",
"description": "One message sent in a chat session.",
"status": "active",
"type": "metered",
"default_value": {},
"metadata": {},
"created_at": "2026-07-20T09:00:00Z",
"updated_at": "2026-07-20T09:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
keyrequired | string | The name your code sends with each use of this metric, and with its metering rule. |
namerequired | string | |
descriptionrequired | string | Longer, free text for dashboards. An empty string unless you set one. |
statusrequired | string | Active or inactive, for your own records. QuotaStack does not use this to block usage today.
|
typerequired | string | What kind of entitlement this metric is, and what a check call returns for it. Fixed after creation.
|
default_valuerequired | object | The starting value for this metric’s If you leave it out: Left out, QuotaStack stores |
metadatarequired | object | Your own key-value tags, unchanged since the last write. |
created_atrequired | string (date-time) | |
updated_atrequired | string (date-time) |
Errors
404— No billable metric here has thiskey. not-found
See also
Update a billable metric
/v1/billable-metrics/{key} | Idempotency-Key | Required. Returns 422 without it. |
|---|---|
| Environment | Sandbox and Live |
| Auth | Tenant API key |
| Writes | Applies the fields you send. metadata and default_value REPLACE the stored object outright — unlike a customer update, neither one merges. |
| Fires | No webhooks. |
This endpoint changes one or more fields on an existing billable metric, found by key. Updating one needs an Idempotency-Key header, like every write. openapi.yaml skips it here, but QuotaStack still checks. A field you skip keeps its old value. type cannot change after creation.
| 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 |
|---|---|---|
keyrequired | string | The billable metric’s own key, exactly as you sent it to |
| Field | Type | Meaning |
|---|---|---|
nameoptional | string | A label for lists and dashboards. If you leave it out: The old value stays. QuotaStack changes nothing. |
descriptionoptional | string | Longer, free text for dashboards. Send an empty string to clear it. If you leave it out: The old value stays. QuotaStack changes nothing. |
statusoptional | string | Marks the metric active or inactive, for your own records. QuotaStack does not use this to block usage today. If you leave it out: The old value stays. QuotaStack changes nothing.
|
typeoptional | string | Must equal the metric’s current If you leave it out: The old value stays. QuotaStack changes nothing.
|
default_valueoptional | object | The starting value for this metric’s If you leave it out: The old value stays. QuotaStack changes nothing. Sending a value REPLACES the stored object outright — it does not merge. |
metadataoptional | object | Your own key-value tags. If you leave it out: The old value stays. QuotaStack changes nothing. Sending a value REPLACES the whole object outright — it does not merge. |
{
"name": "Chat message (v2)"
} {
"id": "0192f5a4-7c31-7b8e-9a2d-4f6c8e1b3a52",
"key": "chat_message",
"name": "Chat message (v2)",
"description": "One message sent in a chat session.",
"status": "active",
"type": "metered",
"default_value": {},
"metadata": {},
"created_at": "2026-07-20T09:00:00Z",
"updated_at": "2026-07-20T11:00:00Z"
} | Field | Type | Meaning |
|---|---|---|
idrequired | string (uuid) | |
tenant_idrequired | string (uuid) | |
keyrequired | string | The name your code sends with each use of this metric, and with its metering rule. |
namerequired | string | |
descriptionrequired | string | Longer, free text for dashboards. An empty string unless you set one. |
statusrequired | string | Active or inactive, for your own records. QuotaStack does not use this to block usage today.
|
typerequired | string | What kind of entitlement this metric is, and what a check call returns for it. Fixed after creation.
|
default_valuerequired | object | The starting value for this metric’s If you leave it out: Left out, QuotaStack stores |
metadatarequired | object | Your own key-value tags, unchanged since the last write. |
created_atrequired | string (date-time) | |
updated_atrequired | string (date-time) |
Errors
404— No billable metric here has thiskey. not-found409— State conflict (e.g., duplicate resource, stale version). On any route that requiresIdempotency-Key, a 409 here can also mean a concurrent duplicate request under the same key is still in flight: the duplicate waited up to 10 seconds for the original to finish and gave up. That case carries aRetry-Afterheader — retry after that many seconds; the original has by then either completed (so the retry replays it) or failed, in which case the retry runs itself.422—typedoes not match the metric’s current type,statusis notactiveorinactive, ordefault_valuedoes not matchtype’s shape. A missingIdempotency-Keyheader returns this too. Sending the metric’s own currenttypeback is fine and changes nothing. validation-error
See also
Loading…