Skip to content

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.

TypeExampleCheck Pattern
Feature gatebrand_kit, data_exportBoolean — allowed or denied
Usage limitapi_calls, storage_gbNumeric — current vs. limit
Creditsrender_creditsDepleting balance
Seatsteam_membersPer-account count limit
Capability tieranalytics_tierPlan-level access (starter → pro → enterprise)

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:

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>;
}

<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.

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
}
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".

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).
StatusMeaning
allowedFull access — proceed normally
limitedAccess granted but approaching limit (e.g., 85% usage)
deniedNo access — show upgrade prompt or block

Report usage to keep entitlement checks accurate:

// Update current balances
sdk.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
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>
);
}
const { allowed } = useEntitlement({ handle: 'brand_kit' });
// allowed: true/false
const { allowed, limited } = useEntitlement({ handle: 'api_calls' });
// limited: true when usage > 80%
// used / limit: read from useUsageSnapshot() (see "Reading Usage")
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
}
const { denied, result } = useEntitlement({ handle: 'advanced_analytics' });
if (denied) {
// result.current_tier: 'professional'
// Show "upgrade to Professional" prompt
}

The SDK uses fail-open behavior — if the entitlement service is unreachable, can returns allowed: true with reason 'entitlement_service_unavailable'. This ensures your app’s baseline UX is never blocked by RevTurbine downtime.

// If API is down:
// { status: 'allowed', allowed: true, reason: 'entitlement_service_unavailable' }