Entitlements
Metering
How a NUMBER entitlement is measured — the six aggregation methods, append versus set, what happens inside a usage report, and the periodic usage windows that reset a counter.
Overview
Metering is the observed side of the contract: how much of a NUMBER (or NUMBER_AI_CREDIT) entitlement an instance has consumed. Kaiten keeps one usage record per entitlement per instance — a running total, the number of reports that produced it, and, for a periodic entitlement, the start of the window the total belongs to. Your product reports; Kaiten folds the report into the record through the entitlement's aggregation method, compares the result with the grant of the instance's License, and either writes it or refuses it.
Your product
│ POST /instances/demo-restaurant-prod/entitlements/monthly-orders/usage
│ { "value": { "type": "number", "value": 1 }, "behavior": "append" }
▼
Kaiten — one transaction
│ resolve the grant (value 100, overage allowance 10 %) and the current window
│ fold the report: SUM → 41 + 1 = 42
│ compare with the maximum allowed usage: 42 ≤ 110 → accepted
▼
{ "value": { "type": "number", "value": 42, "event_count": 42 }, "limit": { "type": "number", "value": 100 },
"currentPeriodStart": "2026-09-01T00:00:00Z", "currentPeriodEnd": "2026-10-01T00:00:00Z" }Only the NUMBER family is metered. A BOOLEAN or CONFIG entitlement has nothing to count — reporting against one is refused with 400.
Examples use $KAITEN_API and $KAITEN_TOKEN as set up in the quickstart, on the seeded Kaiten Sushi Shop data, where the seed leaves demo-restaurant-prod at 41 monthly orders. Responses are illustrative — ids depend on your stack, and the window bounds on the month you run them in.
Aggregation methods
The aggregation method is set on the entitlement, once, and is mandatory for the NUMBER family (SUM when omitted). It decides what the stored total means:
| Method | The total becomes | Kaiten Sushi Shop |
|---|---|---|
SUM | The running sum of every reported value — a counter | monthly-orders: report each order as 1, or a batch as its count |
COUNT | The number of reports, whatever their values | logins, sessions |
LATEST | The last value reported — a gauge | menu-items, delivery-drivers, locations: report the current count |
MAX / MIN | The highest or lowest value seen | peak concurrent kitchen screens |
AVERAGE | The mean of all reported values | average order preparation time |
The method is immutable once set — PUT /entitlements/{slug} accepts it only so a full-replace body can echo the stored value.
Append and set
Every report carries a behavior:
appendfolds the reported value into the stored total through the aggregation method, and incrementsevent_count.setoverwrites the total with the reported value, and resetsevent_countto1— asetis one observation of the counter, so the pair{value, event_count}stays consistent. AnAVERAGEthat follows asetof60with anappendof40therefore reads(60 × 1 + 40) / 2 = 50, the mean of the two observations.
set is how you correct a counter: it writes an absolute value, so correcting 60 down to 25 under a cap of 30 is compared as 25 > 30 — false — and accepted. Only a set that lands above the grant's maximum allowed usage is refused, exactly like an append would be.
Under LATEST, append and set resolve to the same total — "the count is now n" — and differ only in event_count.
Report usage
curl -X POST "$KAITEN_API/instances/demo-restaurant-prod/entitlements/monthly-orders/usage" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "value": { "type": "number", "value": 1 }, "behavior": "append" }'{
"entitlementId": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
"entitlementSlug": "monthly-orders",
"licenseId": "c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f",
"licenseSlug": "standard",
"value": { "type": "number", "value": 42, "event_count": 42 },
"limit": { "type": "number", "value": 100 },
"currentPeriodStart": "2026-09-01T00:00:00Z",
"currentPeriodEnd": "2026-10-01T00:00:00Z"
}| Field | Meaning |
|---|---|
value | The observed side: the stored total after this report, and the number of reports that produced it |
limit | The contract side: the value the instance's License grants, -1 for unlimited, omitted when the License grants nothing for this entitlement |
currentPeriodStart / currentPeriodEnd | The window the total belongs to (start inclusive, end exclusive); omitted for a lifetime counter |
The response does not repeat the grant's overage allowance — read it on the grant itself (GET /licenses/standard/entitlements/monthly-orders, field limitCapExceededOveragePercent). Reporting requires write:instances.
What happens inside a report
One transaction, in this order:
- Resolve the context. The entitlement's type (anything but the
NUMBERfamily is refused with400), its aggregation method, the current usage record, and the grant of the instance's License — its value and itslimitCapExceededOveragePercent. - Resolve the window — only if the entitlement declares a reset period. Kaiten reads the database clock, computes the current window, and if the stored record belongs to an earlier window it rolls it over: the total is reset to
0for the new window and written first, oneINSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVERevent is emitted per closed window, and the cap-reached signal re-arms. The rollover is persisted whatever the incoming report's fate — a refused report does not undo a window that legitimately advanced. - Compute the updated total:
appendthrough the aggregation method, orset. - Decide. If the updated total exceeds the grant's maximum allowed usage,
ENTITLEMENT_USAGE_REPORT_REJECTEDis emitted, the usage is not written, and the call returns409. There is one comparison and no other branch. - Persist the accepted total and emit
ENTITLEMENT_USAGE_REPORT_ACCEPTED. - Signal the crossings: exactly on the value →
INSTANCE_ENTITLEMENT_USAGE_REACHED; across the value inside the allowance →INSTANCE_ENTITLEMENT_CAP_EXCEEDED; across the warning boundary →INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED. The two crossing signals fire once per crossing, never on later reports that stay above; the cap-reached signal fires on every report whose total lands exactly on the value.
The arithmetic of step 4 and the three signals are the subject of Usage & Thresholds.
Periodic usage windows
By default a usage record is a lifetime counter: it accumulates for as long as the instance exists. An entitlement can instead declare a reset period, and its counter then lives in successive windows.
| Property | Values | Meaning |
|---|---|---|
resetPeriod | HOUR, DAY, WEEK, MONTH, YEAR | The cadence at which the counter starts over. Unset means a lifetime counter. |
resetAnchor | CALENDAR, LICENSE_START | Where windows begin: aligned on the UTC calendar (start of the hour, day, week, month, or year), or phased off the instance's startLicenseDate. Required exactly when resetPeriod is set; CALENDAR when omitted. |
Both are properties of the entitlement — the measurement, identical for every License — and both are immutable once set. They belong to the NUMBER family only, and not to a LATEST gauge: "the most recent value" has no meaningful per-window reset. Kaiten Sushi Shop's monthly-orders is MONTH / CALENDAR; its other quotas are lifetime counters.
The rollover is lazy. No background job closes windows: a read after a window ends already shows the new window at 0 and writes nothing, and the next report detects that the stored record belongs to an earlier window and rolls it over, using the database clock so that every replica agrees on the current window. The counter restarts at 0 in the new window, and INSTANCE_ENTITLEMENT_USAGE_REACHED can fire again once it lands on the value.
Every closed window is recorded. The rollover emits one INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER per window closed since the last report — the entitlement's own stored bucket, and one synthetic event per intermediate window that was skipped entirely while the instance was silent — so the audit trail has no gaps:
is_synthetic | The event describes |
|---|---|
false | The real stored bucket of the entitlement, with the value and event_count it closed at |
true | An empty intermediate window, materialized for continuity (value 0, event_count 0) |
Each event carries closed_period_start, closed_period_end, and new_period_start. These events are the only history of a closed window: there is no separate usage-history table, and the usage record itself only ever holds the current window.
Read back, a periodic entitlement shows currentPeriodStart and currentPeriodEnd on every usage response; a lifetime counter omits both. Existing records of an entitlement that never had a reset period stay lifetime counters.
A dormant HOUR or DAY entitlement can close thousands of windows in one report. Kaiten writes all their events in a single batch inside the same transaction, so the report costs one round trip whatever the gap.
Read usage
curl "$KAITEN_API/instances/demo-restaurant-prod/entitlements/monthly-orders/usage" \
-H "Authorization: Bearer $KAITEN_TOKEN"Same shape as the report response. This read is itself recorded in the instance's audit trail as ENTITLEMENT_VALUE_GET.
curl "$KAITEN_API/instances/demo-restaurant-prod/entitlements/usage" \
-H "Authorization: Bearer $KAITEN_TOKEN"An array with one entry per entitlement the License grants, each next to its limit and its window bounds.
curl "$KAITEN_API/entitlement-groups/restaurant-operations/usage?instance=demo-restaurant-prod" \
-H "Authorization: Bearer $KAITEN_TOKEN"One entry per member of the group, with usageValue (omitted when nothing has been reported yet), licenseValue, and the window bounds.
Reading requires read:instances for the instance routes and read:entitlements for the group route.
Choosing a method
| You want to know | Method | Report |
|---|---|---|
| How many orders were processed this month | SUM + resetPeriod: MONTH | 1 per order, or a batch's count |
| How many dishes are on the menu right now | LATEST | The current count, after each change |
| The busiest moment of the day | MAX + resetPeriod: DAY | The current concurrency, periodically |
| How many API sessions were opened | COUNT | Any value — only the number of reports counts |
| The mean preparation time | AVERAGE | Each preparation time |
Two accounting styles work with a counter: one append per event for exact accounting, or a periodic append of the interval's count when call volume matters more than immediacy. Both are compared with the grant at report time; the second moves the moment of refusal to the batch.
Measurement lives on the entitlement, enforcement on the grant. The method and the window say how monthly-orders is counted, the same for every License; how many orders a License grants, and how far past that number a report may go, is written on each grant — see Usage & Thresholds.
Exact reference — the usage record's columns and CHECK constraint: the entitlement_usage table.
Entitlement Groups
Named sets of entitlements — how to create a group, add and remove members, and read the aggregated usage of a group for one instance.
Usage & Thresholds
The two numbers on the grant that decide enforcement, the allowance arithmetic, the exact refusal, the three crossing signals, and how every report and every closed window lands in the audit trail.