For the complete documentation index, see llms.txt. This page is also available as Markdown.

Entitlements

The Entitlements API answers one question: what does this customer's plan actually grant them? Your app calls it to gate its own features, instead of hard-coding a plan-to-feature map that needs a deploy every time a plan changes.

PeakCommerce never needs to know what your feature keys mean. You register your own vocabulary — max_seats, priority_support, whatever your product needs — and PeakCommerce only interprets each key's type (boolean, limit, config), and only to validate values when they are written.

How a value is resolved. For a given subscription and feature, the resolved value is the active per-customer override if one exists, otherwise the plan default carried by the subscription's product. Your app never sees that overrides exist — it gets the final answer. An override with an expiry simply stops winning once that moment passes.

Limits and "unlimited". A limit feature resolves to a non-negative integer, or to null meaning unlimited. null is a real answer, not a missing one — do not treat a null as "no entitlement", or an unlimited plan will gate to zero.

Add-ons do not contribute (yet). A resolved set comes from the subscription's base product only; add-on products attached in the billing system are not merged in. This is why every response carries resolvedFrom — so you can always see which product produced the numbers rather than having to infer it. If a customer needs more than their plan grants today, the supported mechanism is a per-customer override, which staff can grant against the subscription with a reason and an audit trail.

Not to be confused with tenant entitlements. This API is about what your plans grant your customers. What PeakCommerce grants your tenant is a separate, internal concept (platform_entitlements) and is not exposed here.

Both endpoints require the read scope and are scoped to the tenant that owns the API key. An id belonging to another tenant returns 404 — indistinguishable from an id that does not exist, by design.

Endpoints

GET /api/v1/entitlements/contact/{contactId}

Resolve entitlements for every subscription a contact holds

get
/entitlements/contact/{contactId}

Returns one resolved entitlement set per subscription, keyed by subscription id. Deliberately NOT merged into a single object: a contact may hold several subscriptions whose values disagree, and there is no defensible merge rule for conflicting config values.

Authorizations
X-API-KeystringRequired

Scoped secret API key — send an sk_live_… value (keys issued before the typed format carry a bare pk_<hex> prefix and are also secret; they still work). A publishable pk_live_…/pk_test_… key is browser-safe, carries no scopes, and is refused by every endpoint in this spec with publishable_key_not_allowed. Coarse scopes: read, commerce, admin (admin covers everything below). Narrow per-resource scopes: journeys|pages :read|write|publish|delete, productsets :read|write|publish|delete, components|rules :read|write|delete, context :read (read-only context tier), catalog :read|write-native. publish is never implied by write.

Path parameters
contactIdstring · uuidRequired
Responses
200

Resolved entitlement sets, one per subscription

No content

get/entitlements/contact/{contactId}
GET /api/v1/entitlements/contact/{contactId} HTTP/1.1
Host: api.peakcommerce.app
X-API-Key: YOUR_API_KEY
Accept: */*
200

Resolved entitlement sets, one per subscription

No content

Returns one resolved set per subscription, keyed by subscription id — not a single merged object.

That shape is deliberate. A contact can hold more than one subscription, and two of them can disagree: a Starter plan says max_seats: 1 while a Pro plan says max_seats: null (unlimited). Merging would mean silently picking one, and for config-typed features there is no defensible rule at all. So the response makes the multiplicity explicit and your app decides which subscription it is gating.

If you already know the subscription, prefer GET /entitlements/subscription/{subscriptionId}.

An empty subscriptions array means this contact holds no subscriptions in your tenant. That is a valid answer, not an error — gate to nothing.

GET /api/v1/entitlements/subscription/{subscriptionId}

Resolve the entitlement set granted to one subscription

get
/entitlements/subscription/{subscriptionId}

Returns the final, resolved entitlement values for a subscription — the plan defaults merged with any active per-customer override. Overrides are never revealed as such; the response is the answer, not its derivation. resolvedFrom names the product the values came from: add-on products do not contribute in v1.

Authorizations
X-API-KeystringRequired

Scoped secret API key — send an sk_live_… value (keys issued before the typed format carry a bare pk_<hex> prefix and are also secret; they still work). A publishable pk_live_…/pk_test_… key is browser-safe, carries no scopes, and is refused by every endpoint in this spec with publishable_key_not_allowed. Coarse scopes: read, commerce, admin (admin covers everything below). Narrow per-resource scopes: journeys|pages :read|write|publish|delete, productsets :read|write|publish|delete, components|rules :read|write|delete, context :read (read-only context tier), catalog :read|write-native. publish is never implied by write.

Path parameters
subscriptionIdstring · uuidRequired
Responses
200

Resolved entitlement set

No content

get/entitlements/subscription/{subscriptionId}
GET /api/v1/entitlements/subscription/{subscriptionId} HTTP/1.1
Host: api.peakcommerce.app
X-API-Key: YOUR_API_KEY
Accept: */*

No content

The precise form — use this when your app already knows which subscription it is gating.

entitlements is a flat map of your own feature keys to their resolved values. resolvedFrom names the product those values came from; add-on products do not contribute (see the overview).

A subscription with no product resolves to an empty entitlements map rather than an error — "no plan" and "a plan that grants nothing" are different states, and both are answerable.

Here max_seats: null means unlimited, not "unset".

Last updated

Was this helpful?