Entitlements
Exactly how check decides, and every reason it can refuse.
access.check is the call on your request path. It answers whether an account
may do something, and how much room they have left.
const decision = await base316.access.check({
externalId: "acct_1234",
feature: "api_calls",
requested: 1,
});{
"allowed": true,
"usage": {
"used": 412,
"limit": 1000,
"credits": 0,
"remaining": 588,
"unit": "calls",
"overagePriced": false,
"overage": false,
"billableOverage": 0,
"hardCap": null
}
}The rule
For a metered feature, the allowance is:
limit + creditsand only usage beyond that is billable. Prepaid credits extend the plan's limit rather than being spent first, which is why a customer topped up past their limit is not over quota.
A limit declared per unit (limitPerUnit on the plan's entitlement) is
multiplied by the subscription's quantity before it is compared: one hundred
credits per seat on a five-seat subscription is a limit of five hundred. The
check, the entitlement claims in a token and the invoice all scale it the same
way, so an application is never refused for usage the invoice then includes.
requested is how much you are about to consume. Passing it lets the check
refuse before the work happens rather than after — which is the difference
between declining a request and eating the cost of one.
Soft limits and hard caps
Whether the limit refuses depends on whether the plan version prices the
feature's usage — a PER_UNIT, MATRIX, tiered or PACKAGE price carrying the
feature's id:
| The plan version… | Past limit + credits, check… |
|---|---|
| has no usage price on it | refuses with USAGE_LIMIT_EXCEEDED — a hard limit |
| prices the feature's usage | allows, with usage.overage: true, and the usage is billed — a soft limit |
On a soft limit, limit: 0 means every unit is billed; limit: null is still
unlimited and bills nothing. A FLAT price on a feature does not charge per
unit, so it leaves the limit hard. QUANTITY features are always hard: nothing
bills a held count past its limit.
usage says which case you are in:
| Field | Meaning |
|---|---|
overagePriced | The limit is soft: usage past it is billed, not refused |
overage | This request takes usage past limit + credits — it will be billed |
billableOverage | Usage already past limit + credits this period, as the invoice will charge it |
hardCap | The ceiling past which check refuses with HARD_CAP_REACHED; null when there is none |
remaining | limit + credits - used; below zero on a soft limit is how far into overage |
A feature priced per model (a MATRIX price — see
per-model pricing) is still
checked on its total: used, remaining and billableOverage are the
feature's, across every model. GET /v1/customers/{id}/entitlements adds
byDimension — this period's usage per row, rated as the next invoice will rate
it, with unpriced marking values no row prices.
A hard cap is set per entitlement on the plan (hardCap). It is an absolute
ceiling on usage in the period: scaled by quantity like the limit when
limitPerUnit is set, and never raised by credits. It needs a numeric
limit and cannot be below it. On a hard limit it only matters if credits would
carry usage past it.
The cap governs check, not ingestion. An event recorded past it is still
metered and billed — see billing.
Entitlement claims in a token (entitlements scope) carry each feature's limit.
For a soft-limited feature they add entitlements_overage, feature key to hard
cap (null for none): a key there means the number in entitlements is an
allowance billed past, not a ceiling to enforce.
Why it can refuse
reason is present only when refused, and is safe to log.
| Reason | What happened |
|---|---|
CUSTOMER_NOT_FOUND | No customer with that id in this environment |
NO_SUBSCRIPTION | The account has no subscription |
SUBSCRIPTION_INACTIVE | Cancelled, paused or incomplete |
FEATURE_NOT_FOUND | No feature with that key |
ENTITLEMENT_REQUIRED | The plan does not include this feature |
USAGE_LIMIT_EXCEEDED | Past limit + credits on a hard limit |
HARD_CAP_REACHED | Past the entitlement's hard cap |
USER_NOT_FOUND | The acting user does not exist |
USER_DISABLED | The user exists but is disabled |
NOT_A_MEMBER | The user is not in the organisation being checked |
PERMISSION_DENIED | The user's role does not grant the permission |
Show remaining in your own UI before somebody hits the wall. A refusal a
customer could have seen coming is a support ticket.
Every feature at once
check answers one feature. To see every feature for an account — the
customer page's Effective entitlements panel, or a support tool of your own
— ask for all of them:
const { payerId, data } = await base316.customers.entitlements("cus_8f2k");
for (const feature of data) {
console.log(feature.featureKey, feature.allowed, feature.reason, feature.remaining);
}GET /v1/customers/:id/entitlements (secret key) runs the same evaluator as
check, once per live feature, with the default requested of 1 — so a
DENIED here is the refusal your application would get, with the same
reason. Each feature also says what its answer rests on:
source | What it names |
|---|---|
plan | The subscription and plan version deciding it; listed: false means the version omits the feature |
credits | The live credit grants extending the allowance, with what is left on each |
payer | The paying account, when this customer is a managed tenant billed to its parent — whose plan decides |
Add-ons never appear as a source: an add-on is a charge on the invoice and grants no feature. Nothing is consumed by asking.
Checking a permission too
One call can ask both questions:
await base316.access.check({
externalId: "acct_1234",
userId: "usr_abc",
feature: "reports",
permission: "reports.export",
});The account must be entitled to the feature and the acting user must hold the permission. Checking a permission requires a user; checking a feature does not.
Statuses that still entitle
ACTIVE and TRIALING both entitle. So does PAST_DUE — the subscription is
live, dunning is expected to recover the payment, and cutting somebody off the
moment a card fails punishes them for their bank's decision. PAUSED,
CANCELED and INCOMPLETE do not.
What check does not do
It does not record usage. It is a read, every time. Recording is a separate call you make after the work succeeds — see metering.
Keeping them apart is deliberate: work that fails should cost nothing, and a check that billed on inspection would make every retry expensive.

