Headless API
The headless API provides three controllers and a factory function:
| Export | Purpose |
|---|---|
| initRevTurbine() | Async factory — creates an initialized SdkSession |
| SdkSession | Session wrapper with user context, placement, and entitlement APIs |
| PlacementController | Register → decide → impression-track → interact lifecycle |
| EntitlementGate | Check → auto-gate pipeline for feature access |
import { initRevTurbine, PlacementController, EntitlementGate,} from '@revturbine/sdk/headless';Quick Start
Section titled “Quick Start”import { initRevTurbine } from '@revturbine/sdk/headless';
const session = await initRevTurbine({ tenantId: 'tenant_abc', apiKey: 'rt_live_xxx', endpoint: 'https://api.revturbine.io', user: { id: 'user_123', plan: { id: 'pro' } },});
// Placementconst banner = session.placement({ surfaceSlot: { id: 'upsell_banner' } });await banner.load();if (banner.visible) { console.log('Show banner:', banner.content);}
// Entitlementconst gate = session.entitlement({ handle: 'brand_kit', autoGate: true });await gate.check();if (gate.denied) { console.log('Show upgrade gate:', gate.gatedPlacement);}initRevTurbine(options)
Section titled “initRevTurbine(options)”Creates a fully-initialized session. Handles SDK initialization, user identification, theme resolution, and optional placement bootstrapping.
const session = await initRevTurbine({ tenantId: 'tenant_abc', apiKey: 'rt_live_xxx', endpoint: 'https://api.revturbine.io', user: { id: 'user_123', plan: { id: 'pro' } }, runtimeMode: 'revturbine_server', bootstrapPlacements: [ { placement: { name: 'pricing_banner' } }, ],});Options
Section titled “Options”Extends RevTurbineInitInputOptions with:
| Option | Type | Description |
|---|---|---|
| bootstrapPlacements | Array<{placement, userId?, contextMode?, overrides?, traits?, ttlMs?}> | Placements to preload on creation |
All standard init options apply: tenantId, apiKey, endpoint, runtimeMode, user, localRuntime, provider, providerFallbacks, etc.
SdkSession
Section titled “SdkSession”The session returned by initRevTurbine(). Provides the full imperative API.
User Context
Section titled “User Context”session.identify('user_456', { plan: { id: 'enterprise' } });session.setUserContext({ personalization: { company: 'Acme' } });const ctx = session.getUserContext();session.resetIdentity();session.updateUsage({ api_calls: 150 });const fullCtx = await session.fetchUserContext('user_456');const trial = await session.getTrialStatus();const usage = session.getUsage();Factory Methods
Section titled “Factory Methods”const ctrl = session.placement({ surfaceSlot: { id: 'upsell_banner' } });const gate = session.entitlement({ handle: 'brand_kit', autoGate: true });const decision = await session.getPlacementBySlotId('upsell_banner');const result = await session.checkEntitlement('brand_kit');await session.trackEvent('feature_used', { feature: 'export_csv' });PlacementController
Section titled “PlacementController”Manages the full placement lifecycle: register → decide → impression-track → interact.
Basic Usage
Section titled “Basic Usage”const ctrl = session.placement({ surfaceSlot: { id: 'pricing_banner' },});
await ctrl.load();
if (ctrl.visible) { renderBanner(ctrl.content);}Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
| surfaceSlot | {id, name?} | — | Canonical surface slot (preferred) |
| placement | RevTurbinePlacementConfig | — | Alternative: placement config |
| userId | string | SDK user | Override target user |
| contextMode | RevTurbineContextMode | 'auto' | Context resolution mode |
| overrides | RevTurbinePlacementDecisionOverrides | — | Override segment/plan/usage for testing |
| traits | Record<string, string \| number \| boolean> | — | Custom traits for decision request |
| ttlMs | number | — | Decision cache TTL |
| autoTrackImpression | boolean | true | Auto-record impression on visible decision |
State & Getters
Section titled “State & Getters”ctrl.visible; // boolean — is the placement visible?ctrl.content; // resolved content or nullctrl.decision; // full decision object or nullctrl.placementId; // registered placement IDctrl.state; // full PlacementControllerState snapshotInteractions
Section titled “Interactions”await ctrl.dismiss(); // dismiss with 24h cooldownawait ctrl.dismiss(7200_000); // custom cooldown (ms)await ctrl.snooze(); // snooze for 1 hourawait ctrl.snooze(7200); // custom snooze (seconds)await ctrl.ctaClick('upgrade'); // CTA clickawait ctrl.ctaComplete('upgrade'); // CTA completed (hides placement)await ctrl.refresh(); // re-fetch decisionctrl.reset(); // reset all stateReactive Updates
Section titled “Reactive Updates”const unsub = ctrl.onChange(() => { console.log('State changed:', ctrl.state); updateUI(ctrl.state);});
unsub(); // stop listeningEntitlementGate
Section titled “EntitlementGate”Checks feature access and optionally resolves a gated placement for denied users.
Basic Usage
Section titled “Basic Usage”const gate = session.entitlement({ handle: 'brand_kit', autoGate: true,});
await gate.check();
if (gate.allowed) { enableFeature();} else if (gate.denied && gate.gatedPlacement) { showUpgradeModal(gate.gatedPlacement);}Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
| handle | string | — | Entitlement handle (e.g. 'brand_kit') |
| context | RevTurbineEntitlementContext | — | Optional usage/tier context |
| autoGate | boolean | false | Auto-resolve gated placement when denied |
| gatePlacementRequest | Omit<...> | — | Custom placement request for auto-gate |
State & Getters
Section titled “State & Getters”gate.allowed; // booleangate.limited; // boolean — usage partially exhaustedgate.denied; // booleangate.result; // raw EntitlementResult or nullgate.gatedPlacement; // PlacementOutput (when denied + autoGate)gate.state; // full EntitlementGateState snapshotReactive Updates
Section titled “Reactive Updates”const unsub = gate.onChange(() => { updateAccessUI(gate.state);});
await gate.recheck(); // re-run the checkFramework Integration Patterns
Section titled “Framework Integration Patterns”const session = await initRevTurbine({ /* ... */ });
const banner = session.placement({ surfaceSlot: { id: 'top_banner' } });banner.onChange(() => { const el = document.getElementById('banner'); if (!el) return; el.style.display = banner.visible ? 'block' : 'none'; if (banner.content) { el.innerHTML = banner.content.headline || ''; }});await banner.load();
document.getElementById('dismiss-btn')?.addEventListener('click', () => { banner.dismiss();});import { ref, onMounted, onUnmounted } from 'vue';import { initRevTurbine } from '@revturbine/sdk/headless';
export function usePlacement(slotId: string) { const state = ref({ visible: false, content: null }); let unsub: (() => void) | undefined;
onMounted(async () => { const session = await initRevTurbine({ /* ... */ }); const ctrl = session.placement({ surfaceSlot: { id: slotId } }); unsub = ctrl.onChange(() => { state.value = { visible: ctrl.visible, content: ctrl.content }; }); await ctrl.load(); });
onUnmounted(() => unsub?.()); return state;}import { writable } from 'svelte/store';import { initRevTurbine } from '@revturbine/sdk/headless';
export function createPlacementStore(slotId: string) { const store = writable({ visible: false, content: null });
(async () => { const session = await initRevTurbine({ /* ... */ }); const ctrl = session.placement({ surfaceSlot: { id: slotId } }); ctrl.onChange(() => { store.set({ visible: ctrl.visible, content: ctrl.content }); }); await ctrl.load(); })();
return store;}Comparison: Headless vs React
Section titled “Comparison: Headless vs React”| Concern | React hooks | Headless controllers |
|---|---|---|
| State management | React useState | Manual via onChange() |
| Lifecycle | useEffect | Explicit load() / check() |
| Cleanup | Hook unmount | Call unsub() / reset() |
| Framework | React only | Any JS runtime |
| Bundle | Includes React dep | Zero framework deps |
The React hooks (usePlacement, useEntitlement) delegate to the headless controllers internally — they share the same business logic.
Try It Live
Section titled “Try It Live”Explore headless scenarios in the interactive playground: