Endpoints are better-auth endpoints. Call them through auth.api on the server (with { headers, body } or { headers, query }), or through authClient.entitlements.* in the browser. Each is one of the kinds in the BYO conventions:
- gated needs a session and an
entitlementspermission, in the target organization or the staff organization (see Overrides and access); - server-only is never an HTTP route;
- server function is a plain function you import and call with an organization id. It checks no session, so put your own tenant checks in front of it.
There is no credential-free route.
Endpoints
auth.api key |
Method and path | Kind | Input | Returns |
|---|---|---|---|---|
getEntitlements |
GET /entitlements/get |
gated: entitlements:read in organizationId, or in the staff organization |
query organizationId |
The get result plus usage per metered key ({ used, remaining, resetAt }). Staff callers also get each override’s id, reason and createdBy |
refreshEntitlements |
POST /entitlements/refresh |
gated, same rule | body organizationId |
The same, after a sync that bypasses the cache. Call it when the user returns from checkout |
setEntitlementOverride |
POST /entitlements/override/set |
gated: entitlements:override in the staff organization |
body organizationId, kind, key, value?, reason, expiresAt? (value is required for feature and limit and ignored for plan) |
{ override } |
removeEntitlementOverride |
POST /entitlements/override/remove |
gated: entitlements:override in the staff organization |
body id |
{ ok: true } |
listEntitlementOverrides |
GET /entitlements/overrides |
gated: entitlements:read in the staff organization |
query organizationId?, status?, cursor? |
{ overrides, nextCursor }, newest first, up to 50 per page |
processEntitlementsSync |
none | server-only | none | { synced, skipped, retrying, failed, expired, pruned, hasMore }. See Background work |
The client exposes them as authClient.entitlements.get({ query }), .refresh({ organizationId }), .override.set(body), .override.remove({ id }) and .overrides({ query }). Over HTTP, dates arrive as ISO strings; the Wire<T> type describes that shape.
getEntitlements and refreshEntitlements answer 503 ENTITLEMENTS_UNAVAILABLE instead of a degraded plan, so a UI never shows the default plan as if it were real. refreshEntitlements inside a transaction throws instead.
The override endpoints accept any body and check it after the staff gate, so a caller without access always gets 404, whatever it sends. An unauthenticated call answers 401.
Server functions
createEntitlements(auth, catalog) returns the first eight, typed by your catalog. deleteOrganizationData and exportEntitlementsData are separate imports from @byo-oss/entitlements.
| Function | Kind | Returns |
|---|---|---|
get(orgId) |
server function | organizationId, plan (key, display, source), status, periodEnd, cancelAtPeriodEnd, features, limits, the applied overrides ({ kind, key, expiresAt }), degraded |
getMany(orgIds) |
server function | get for up to 1000 ids, in order |
hasFeature(orgId, feature) |
server function | { allowed, reason, key, plan, upgradeTo } |
getLimit(orgId, key) |
server function | { key, plan, limit, allowed, reason, upgradeTo, degraded }; metered keys add { used, remaining, resetAt } |
consumeUsage(orgId, key, { amount? }) |
server function | { allowed, reason, key, used, limit, remaining, resetAt, firstRefusal, plan, upgradeTo } |
checkCapacity(orgId, key, { count, adding? }) |
server function | { allowed, reason, key, count, limit, over, plan, upgradeTo } |
sync(orgId) |
server function | The fresh get result. Throws on failure |
invalidate(orgId) |
server function | Nothing. Clears this process’s cache entry |
deleteOrganizationData(auth, orgId) |
server function | Nothing. Deletes the organization’s rows, see Background work |
exportEntitlementsData(auth, options) |
server function | One page of rows, see Outgrowing this |
What each check answers, and what throws, is in Checks.
Error codes
ENTITLEMENTS_ERROR_CODES is exported and set as the plugin’s $ERROR_CODES. Responses are { code, message }.
| Code | Status | When |
|---|---|---|
ORGANIZATION_PLUGIN_REQUIRED |
thrown at boot | organization() is not in plugins |
ORGANIZATION_NOT_FOUND |
404 | Every gate failure, and any unknown organization |
SESSION_NOT_ACCEPTED |
403 | acceptSession returned false |
NOT_ALLOWED |
403 | Reserved; gates answer 404 |
INVALID_OVERRIDE |
400 | An override failed validation |
OVERRIDE_NOT_FOUND |
404 | Removing a missing or non-active override |
ENTITLEMENTS_UNAVAILABLE |
503 | A degraded read, a sync that threw, or a usage read that threw |
INVALID_CURSOR |
400 | A malformed list cursor, or a page past the tie bound |
UNKNOWN_KEY |
thrown Error, not HTTP |
A key of the wrong kind from untyped callers |
Exports
From the root, @byo-oss/entitlements exports entitlements, createEntitlements, deleteOrganizationData, exportEntitlementsData, entitlementsStatements, ENTITLEMENTS_ERROR_CODES, ENTITLEMENTS_EVENT_TYPES, defineEntitlements, upgradeTo, and the types: EntitlementsOptions, BillingSource, BillingState, EntitlementsResult, the check results, EntitlementsEvent, the row types and the rest.
| Import | Exports |
|---|---|
@byo-oss/entitlements/catalog |
defineEntitlements, upgradeTo and the catalog types (Catalog, FeatureKey, LimitKey, MeteredKey, CapacityKey, ValueKey, PlanKey, …). Imports nothing |
@byo-oss/entitlements/client |
entitlementsClient, the catalog helpers and types, entitlementsStatements, and the response types EntitlementsResponse, EntitlementOverride, ListOverridesResponse, UsageInfo and Wire |
@byo-oss/entitlements/react |
useEntitlements, EntitlementGate, UsageMeter and defaultLabels |
@byo-oss/entitlements/stripe |
fromBetterAuthStripe and stripePlans |
@byo-oss/entitlements/testing |
memorySource and testBillingSource (ESM only, needs vitest) |