Entitlements
Entitlements define what users can do based on their plan. The SDK evaluates entitlements locally (in local_only mode) or via the RevTurbine API, and provides React hooks and imperative APIs for access control.
Entitlement Types
Section titled “Entitlement Types”| Type | Example | Check Pattern |
|---|---|---|
| Feature gate | brand_kit, data_export | Boolean — allowed or denied |
| Usage limit | api_calls, storage_gb | Numeric — current vs. limit |
| Credits | render_credits | Depleting balance |
| Seats | team_members | Per-account count limit |
| Capability tier | analytics_tier | Plan-level access (starter → pro → enterprise) |
Checking Entitlements
Section titled “Checking Entitlements”React Hook — useCan
Section titled “React Hook — useCan”The simplest reactive check. useCan(handle) returns { can, limited, result }. can is true while the user is entitled — including the limited (running-low) state — so gate on !can, never !allowed:
Full surface — useEntitlement
Section titled “Full surface — useEntitlement”For the complete reactive result (allowed / denied / limited / isLoading / error / gatedPlacement / recheck), use useEntitlement:
import { useEntitlement } from '@revturbine/sdk';
function ExportButton() { const { allowed, denied, isLoading } = useEntitlement({ handle: 'data_export', });
if (isLoading) return <button disabled>Loading…</button>; if (denied) return <button disabled>Upgrade to Export</button>;
return <button>Export Video</button>;}Declarative gate — <Gate>
Section titled “Declarative gate — <Gate>”<Gate> gates its children declaratively. can="handle" checks an entitlement (shorthand for check={{ entitlement: 'handle' }}); children render while entitled, and a configured upgrade placement — or deniedFallback — shows on denial. limitedFallback renders a soft “running low” state while access is still granted:
A <Gate> is also a placement. On denial the gate doesn’t render hardcoded UI — it resolves a placement from your monetization config and renders it in place of the children. So what a denied user sees — the upgrade modal or banner, its headline, offer, and CTA — is authored in the studio (or the CLI config), not baked into your app. Your PM, growth, or marketing team can rewrite the denial copy, swap the offer, or A/B-test it with no code deploy. surfaceTemplateIds scopes which surface templates can fill the gate; deniedFallback is the static backstop shown only when no placement is configured for it.
Live gate examples
Section titled “Live gate examples”Edit the code in any of these to see the gate respond to different demo users — all run in local_only mode with no backend.
Data Export gate — a blocking modal gate on a paid feature.
Branding gate — an inline gate on a branding feature.
Brand Kit gate — a card gate on a brand-kit feature.
With Auto-Gate
Section titled “With Auto-Gate”When autoGate: true, the hook automatically resolves an upgrade placement if the entitlement is denied:
const { allowed, denied, gatedPlacement } = useEntitlement({ handle: 'data_export', autoGate: true, gatePlacementRequest: { slotId: 'export_gate', surfaceTemplateIds: ['modal_overlay'], },});
if (denied && gatedPlacement) { // Render gatedPlacement.content as an upgrade prompt}Imperative API
Section titled “Imperative API”const result = await sdk.can('data_export');
// result.status: 'allowed' | 'limited' | 'denied'// result.allowed: boolean// result.reason: string (optional)// result.current_tier: string (optional, for tier-based)// Usage numbers (used/limit/remaining) come from useUsageSnapshot — see "Reading Usage".Entitlement Result
Section titled “Entitlement Result”Every entitlement check returns an EntitlementResult:
interface EntitlementResult { status: 'allowed' | 'limited' | 'denied'; allowed: boolean; reason?: string; current_tier?: string;}// Usage numbers (used / limit / remaining) are NOT on the result —// read them from useUsageSnapshot() (see "Reading Usage" below).| Status | Meaning |
|---|---|
allowed | Full access — proceed normally |
limited | At or past a limit. Read allowed, not this — under the default (unset) enforcement limited carries allowed: false; only degrade and allow_overage pair it with allowed: true. There is no “approaching” state. |
denied | No access — show upgrade prompt or block |
Usage Tracking
Section titled “Usage Tracking”Report usage to keep entitlement checks accurate:
// Update current balancessdk.update({ api_calls: 950, storage_gb: 45, team_members: 5,});update() triggers:
- Re-evaluation of usage-based entitlements
- Segment re-evaluation (targeting rules may change)
- Decision cache invalidation
- Local runtime state persistence
Reading Usage
Section titled “Reading Usage”import { useUsageSnapshot } from '@revturbine/sdk';
function UsagePanel() { const { usage, refresh } = useUsageSnapshot();
return ( <ul> {Object.entries(usage).map(([handle, data]) => ( <li key={handle}> {handle}: {data.current} / {data.limit ?? '∞'} </li> ))} </ul> );}Patterns by Entitlement Type
Section titled “Patterns by Entitlement Type”Feature Gate
Section titled “Feature Gate”const { allowed } = useEntitlement({ handle: 'brand_kit' });// allowed: true/falseUsage Limit
Section titled “Usage Limit”const { allowed, limited } = useEntitlement({ handle: 'api_calls' });// limited: true when usage > 80%// used / limit: read from useUsageSnapshot() (see "Reading Usage")Credits
Section titled “Credits”const { limited, result } = useEntitlement({ handle: 'render_credits' });// result.status / result.allowed reflect access as credits run low// remaining balance: read from useUsageSnapshot() (see "Reading Usage")const { denied, result } = useEntitlement({ handle: 'team_members' });if (denied) { // seat counts: read from useUsageSnapshot() (see "Reading Usage") // Show "seat limit reached" prompt}Capability Tier
Section titled “Capability Tier”const { denied, result } = useEntitlement({ handle: 'advanced_analytics' });if (denied) { // result.current_tier: 'professional' // Show "upgrade to Professional" prompt}Fail-Closed Semantics
Section titled “Fail-Closed Semantics”Entitlement checks are fail-closed — if a check can’t produce an affirmative grant, can returns allowed: false rather than granting access. The reason names the cause, so you can still tell an outage apart from a real denial:
// Server mode, the launched Playbook could not be fetched:// { status: 'denied', allowed: false, reason: 'config_unavailable' }
// Local mode, no Playbook and no cached result:// { status: 'denied', allowed: false, reason: 'entitlement_not_in_playbook' }
// A real denial — a rule decided this:// { status: 'denied', allowed: false, reason: 'no_matching_entitlement_rule' }See Entitlement reason codes for the complete emitted set.
Next Steps
Section titled “Next Steps”- Placements Guide — render upgrade prompts for denied entitlements
- Events & Analytics — track entitlement check outcomes
- Error Handling — fail-closed checks and recovery patterns
- Try it live → Data Export Gate