Entitlements.
An entitlement answers one question: is this customer allowed to use this feature right now? You define a feature with a string key (for example pro or api_access), and check it with a single call. The answer comes from two independent sources: an active subscription on a price that carries the same key, or a manual grant you wrote directly. You never have to map plans to features yourself in your own code.
The two sources
A check returns active when either source says so:
subscription— the customer has an active or trialing subscription on a price whoseentitlementKeymatches the key. This is computed live from the subscription state — there is no row to keep in sync. When the subscription ends, the entitlement stops being active on its own.manual— you granted the key directly with agrantcall. Manual grants are independent of any subscription — useful for comps, lifetime access, support overrides, or access bought outside a recurring plan.
customerId (OpenSettle's id) or appUserId (your own user id). Set appUserIdon a customer at create or update so you can check entitlements with the same id your app already uses, without storing OpenSettle's.Tag a price with a key
To make a subscription grant access, set entitlementKey on the price when you create it. Any active or trialing subscription on that price then grants the matching key — no further wiring.
const price = await os.products.createPrice("prod_...", {
amount: 2900, // $29.00 / cycle, minor units
currency: "USD",
interval: "month",
entitlementKey: "pro", // subscriptions on this price grant "pro"
});
price.entitlementKey; // "pro"Set an app user id on a customer
Pass appUserId when you create or update a customer. It is readable back on the customer object, and any entitlement call accepts it in place of customerId.
const customer = await os.customers.create({
email: "ada@example.com",
appUserId: "user_42", // your own user id
});
customer.appUserId; // "user_42"Check access
Check on every gated request. It is available to any workspace member, takes no idempotency key, and returns the resolved entitlement. When active, source tells you why — a subscription (with its subscriptionId) or a manual grant; when inactive, source is null.
const entitlement = await os.entitlements.check({
key: "pro",
appUserId: "user_42", // or: customerId: "cus_..."
});
if (!entitlement.active) {
// gate the feature
}
entitlement.active; // true
entitlement.key; // "pro"
entitlement.source; // "subscription" | "manual" | null
entitlement.subscriptionId; // set when source is "subscription"
entitlement.expiresAt; // ISO string or nullcurl -X POST https://api.opensettle.io/v1/workspaces/$WORKSPACE_ID/entitlements/check \
-H "Authorization: Bearer $OPENSETTLE_KEY" \
-H "Content-Type: application/json" \
-d '{ "key": "pro", "appUserId": "user_42" }'Grant manually
A manual grant gives a key to a customer independent of any subscription. It requires the developer role and takes an Idempotency-Key header so a retried grant is safe. Pass an optional expiresAt for a time-boxed grant (omit it for one that does not expire) and optional metadata. It returns 201 with the created entitlement.
const entitlement = await os.entitlements.grant({
key: "pro",
appUserId: "user_42", // or: customerId: "cus_..."
expiresAt: "2027-01-01T00:00:00Z", // optional; omit for no expiry
metadata: { reason: "lifetime_comp" },// optional
});
entitlement.id; // "ent_..."
entitlement.status; // "active"
entitlement.source; // "manual"curl -X POST https://api.opensettle.io/v1/workspaces/$WORKSPACE_ID/entitlements/grant \
-H "Authorization: Bearer $OPENSETTLE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "key": "pro", "appUserId": "user_42" }'Revoke a manual grant
Revoke removes a manual grant of a key from a customer. It requires the developer role, is idempotent (revoking an already-revoked or never-granted key is not an error), and returns the entitlement.
await os.entitlements.revoke({
key: "pro",
appUserId: "user_42", // or: customerId: "cus_..."
});curl -X POST https://api.opensettle.io/v1/workspaces/$WORKSPACE_ID/entitlements/revoke \
-H "Authorization: Bearer $OPENSETTLE_KEY" \
-H "Content-Type: application/json" \
-d '{ "key": "pro", "appUserId": "user_42" }'revoke only clears a manual grant — it cannot cancel a subscription. If a customer still has an active subscription on a matching price, a check stays active after you revoke. To stop access that comes from a subscription, cancel the subscription.List a customer's entitlements
List every entitlement for one customer — both subscription-derived and manual — keyed by customerId or appUserId.
const entitlements = await os.entitlements.list({
appUserId: "user_42", // or: customerId: "cus_..."
});
for (const e of entitlements) {
e.key; // "pro"
e.active; // true
e.source; // "subscription" | "manual" | null
}curl -H "Authorization: Bearer $OPENSETTLE_KEY" \
"https://api.opensettle.io/v1/workspaces/$WORKSPACE_ID/entitlements?appUserId=user_42"Webhooks
Manual grant and revoke emit these events so you can sync access in your own system.
entitlement.granted— a key was granted to a customer with a manualgrantcall.entitlement.revoked— a manual grant of a key was revoked from a customer.
These cover the manual grant lifecycle. Subscription-derived access has no separate entitlement event — it is computed live, so follow the subscription events (created, renewed, canceled) to know when that access starts and stops.