Server-Side Integration
The headless SDK runs on Node.js and edge runtimes. In Server mode it fetches
the launched Playbook and evaluates each entitlement and placement in-process;
in LocalOnly mode you bundle the Playbook yourself and make no decisioning
network calls.
When to Use Server-Side
Section titled “When to Use Server-Side”| Use case | Client SDK | Server SDK |
|---|---|---|
| Render placements in React | ✅ | — |
| Gate API endpoints | — | ✅ |
| Enforce usage limits before processing | — | ✅ |
| SSR placement decisions | ✅ (local mode) | ✅ |
| Track server-side events | — | ✅ |
Headless Server Re-check
Section titled “Headless Server Re-check”Use the ./headless entry so a server or edge function does not load React.
Always repeat money-, data-, or compute-sensitive checks at this boundary:
import { initRevTurbine, RuntimeMode } from '@revturbine/sdk/headless';
async function handleExport(req) { const rt = await initRevTurbine({ tenantId: process.env.REVTURBINE_TENANT_ID!, apiKey: process.env.REVTURBINE_API_KEY!, endpoint: process.env.REVTURBINE_ENDPOINT!, mode: 'snippet', runtimeMode: RuntimeMode.Server, user: { id: req.user.id, plan_handle: req.user.planHandle, usage: req.user.usage, }, });
const result = await rt.can('data_export'); if (!result.allowed) { return new Response('Upgrade required', { status: 403 }); }
await rt.track('data_export_started'); await rt.flushEvents(); return runExport(req);}Server mode refreshes the launched Playbook from the control plane, then
evaluates locally. A fetch failure denies access rather than granting it.
Server-Owned CTA Actions
Section titled “Server-Owned CTA Actions”An authored CTA such as extend_trial starts in the browser but must mutate
state on your server. Register a serverActions handler with the client SDK;
it calls your route and returns fresh server-authoritative context:
const options = { // ...your normal RevTurbineProvider options serverActions: { extend_trial: async ({ placement, params }) => { const response = await fetch('/api/trial/extend', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ decisionId: placement.decision_id, extensionDays: params.extension_days, }), });
if (!response.ok) return { success: false }; return { success: true, userContext: await response.json() }; }, },};The route repeats the entitlement check before it changes anything:
import { initRevTurbine, RuntimeMode } from '@revturbine/sdk/headless';
export async function POST(request: Request) { const user = await requireUser(request); const rt = await initRevTurbine({ tenantId: process.env.REVTURBINE_TENANT_ID!, apiKey: process.env.REVTURBINE_API_KEY!, endpoint: process.env.REVTURBINE_ENDPOINT!, mode: 'snippet', runtimeMode: RuntimeMode.Server, user: { id: user.id, plan_handle: user.planHandle }, });
const access = await rt.can('trial_extension'); if (!access.allowed) return new Response('Not allowed', { status: 403 });
const trial = await extendTrial(user.id); return Response.json({ plan_handle: user.planHandle, trial: { in_trial: true, days_remaining: trial.daysRemaining }, });}The SDK contains rejection and { success: false }, leaves held context
unchanged, and records the result on the canonical placement_interaction
event. There is no client-side extendTrial() mutation.
Bundled Local Runtime
Section titled “Bundled Local Runtime”The local runtime evaluates entitlements and placements entirely from a Playbook JSON snapshot. No API server connection required.
Installation
Section titled “Installation”pnpm add @revturbine/sdkEntitlement Checks
Section titled “Entitlement Checks”import { initRevTurbine, RuntimeMode } from '@revturbine/sdk/headless';import playbook from './revturbine.playbook.json';
const sdk = await initRevTurbine({ runtimeMode: RuntimeMode.LocalOnly, localRuntime: { playbook }, user: { id: 'user_123', plan_handle: 'starter', entitlements: { data_export: false }, },});
// Check an entitlementconst result = await sdk.can('data_export');
if (!result.allowed) { // deny access — user needs to upgrade console.log('Upgrade required:', result.status);}Placement Decisions (SSR)
Section titled “Placement Decisions (SSR)”Resolve placements server-side for SSR or email templates:
import { initRevTurbine, RuntimeMode } from '@revturbine/sdk/headless';import playbook from './revturbine.playbook.json';
const sdk = await initRevTurbine({ runtimeMode: RuntimeMode.LocalOnly, localRuntime: { playbook }, user: { id: req.user.id, plan_handle: req.user.planHandle, },});
// Report current usage (balances live under `usage`; a bare handle is not// a usage report)sdk.update({ usage: { api_calls: req.user.apiCallCount } });
// Resolve a specific slot's placementconst banner = await sdk.getPlacement({ slotId: 'dashboard_banner' });
if (banner) { // Render banner.content into your template}SSR with React (Local Mode)
Section titled “SSR with React (Local Mode)”For frameworks like Next.js, resolve placements with the local runtime and hydrate client-side:
'use client';
import { RevTurbineProvider, Slot, RuntimeMode } from '@revturbine/sdk';import playbook from './revturbine.playbook.json';import { useMemo } from 'react';
export default function Dashboard({ user }) { const options = useMemo(() => ({ runtimeMode: RuntimeMode.LocalOnly, localRuntime: { playbook }, user: { id: user.id, plan_handle: user.planHandle, entitlements: user.entitlements, }, }), [user]);
return ( <RevTurbineProvider options={options}> <Slot id="dashboard_banner" surfaceTemplateIds={["banner_placement"]} /> {/* rest of your dashboard */} </RevTurbineProvider> );}Client Sessions
Section titled “Client Sessions”RevTurbineServer, exported from @revturbine/sdk/headless, mints short-lived,
browser-safe client sessions from a secret server key. It is available today,
but it is not the decision evaluator; use the headless session above for checks.
Python server SDK
Section titled “Python server SDK”A Python server SDK is also available, with byte-identical decisioning parity to the Node SDK:
| Language | Package | Status |
|---|---|---|
| Node.js / edge | @revturbine/sdk/headless | ✅ Available |
| Python | revturbine (PyPI) | ✅ Available |
Python exposes the same re-check question as can():
result = sdk.can("data_export")if not result["allowed"]: raise PermissionError(result["reason"])See the Python SDK guide for setup and usage.
Next Steps
Section titled “Next Steps”- Entitlements Guide — entitlement types and checking patterns
- Runtime Modes — choosing the right runtime mode
- Headless API — imperative SDK usage without React