This is the full developer documentation for RevTurbine SDK
# Start
> One command, one , a working feature gate in about ten minutes with no account — then connect Stripe and change monetization without a deploy.
RevTurbine is monetization for product-led SaaS in one SDK: plans, entitlements, usage limits, trials, and the upgrade prompts that convert — decided per user, inside your app, from a Playbook you keep. We add no latency and our availability never affects your product: the SDK evaluates your Playbook locally, so decisions never depend on our servers.
This page is the whole happy path. It runs in local mode first — no account, no key — and ends with hosted mode, where the same Playbook changes without a deploy.
**Note — Not using React?:**
The walkthrough is React because that is the shortest path to something on screen. The engine is framework-agnostic: `import { initRevTurbine } from '@revturbine/sdk/headless'` pulls in no React and runs on Svelte, Vue, plain TypeScript, and Node. Same checks, same decisions; you render the resolved content yourself. See [Headless API](/docs/guides/headless-api/) and [Server-Side Integration](/docs/getting-started/server-side/). Python and Rust SDKs evaluate the same Playbook with byte-identical results.
## 1. Bootstrap
**With your coding agent**
Paste this into Claude Code, Cursor, or whichever agent you use. The scaffold installs the RevTurbine agent skills, so the agent knows the rest.
```text
Run `npm create revturbine@latest` in this project. Then follow the
revturbine-start-here skill: wrap the app in in local
mode with the generated revturbine.playbook.json, and replace ONE
non-critical hard-coded plan check with . Do not touch server-side
enforcement. Show me the diff before you finish.
```
Starting a new app instead? Scaffold it with its framework first (for example `create-next-app`), then paste this:
```text
Run `npm create revturbine@latest` in this new app. Then follow the
revturbine-start-here skill and build monetization in from the start: put
every plan, limit and upgrade prompt in revturbine.playbook.json, never in
app code, and run `npx revturbine validate ./revturbine.playbook.json` after
each change. Gate features with or can(), and re-check anything paid
on the server. Show me the Playbook and the diff before you finish.
```
The fuller prompts for both cases — and the CLI loop an agent uses to keep changing monetization afterwards — are on [Add RevTurbine with a Coding Agent](/docs/guides/coding-agent/).
**By hand**
```bash
npm create revturbine@latest
```
That installs `@revturbine/sdk`, pins `@revturbine/cli` to the project, drops a local-mode `revturbine.playbook.json` with a free and a pro plan, one gated feature and one upgrade prompt, and installs the agent skills. `npx revturbine create` and `npx @revturbine/cli init` run the same command. Already have an account? `revturbine download --live --save ./revturbine.playbook.json` pulls your real Playbook instead.
## 2. Wrap your app
Local mode, no account: the Playbook you just generated is the whole configuration.
```tsx title="src/App.tsx"
import { RevTurbineProvider } from '@revturbine/sdk';
import playbook from './revturbine.playbook.json';
import { useMemo } from 'react';
export default function App() {
const options = useMemo(() => ({
localRuntime: { playbook },
user: { id: 'user_123', plan_handle: 'starter' },
}), []);
return (
);
}
```
`plan_handle` is the plan's handle from your Playbook — the value plan-scoped rules match on. In hosted mode it arrives from Stripe on its own (step 7); here you supply it.
## 3. Gate a feature
`` renders its children when the user is entitled. When they aren't, it renders the upgrade prompt your Playbook configures for that gate — copy, offer and CTA authored in config, not in your app.
```tsx title="src/components/BatchExport.tsx"
import { Gate } from '@revturbine/sdk';
export function BatchExport() {
return (
);
}
```
Start the dev server. With the starter Playbook's free plan, the gate shows the upgrade prompt; change `plan_handle` to `'pro'` and the button appears.
## 4. Check and protect in code
For anything that isn't a React subtree — an event handler, a keyboard shortcut, a server route — ask the SDK directly.
```tsx
import { useRevTurbine } from '@revturbine/sdk';
function ExportMenu() {
const { sdk: rt } = useRevTurbine();
async function onExport() {
// Ask: the answer, and nothing else.
const access = await rt.can('batch_export');
// Ask and act: runs exportPdf only when entitled; hands back the
// entitlement so you can open the upgrade prompt otherwise.
const gated = await rt.gate('export_pdf', () => exportPdf());
if (!gated.ran) openUpgrade(gated.entitlement);
}
return ;
}
```
`can()` returns `{ status, allowed, reason }` — plus `limit` / `used` / `remaining` when a usage or credit rule matched. See [Entitlements](/docs/guides/entitlements/).
## 5. Add a slot
A `` is a spot where RevTurbine may show something — a banner, a modal, a toast, a button. Which placement fills it, for which user, with what copy and how often, is decided from the Playbook: targeting, caps, cooldowns and priority between competing prompts are the engine's job, not yours.
```tsx title="src/components/Dashboard.tsx"
import { Slot } from '@revturbine/sdk';
export function Dashboard() {
return (
Dashboard
{/* An upgrade banner for the users the Playbook targets — or nothing */}
);
}
```
When a user clicks a CTA inside a placement, the SDK calls a resolver you provide — `navigate_to_plans`, `open_checkout_modal`, and so on — so navigation and checkout stay in your code. The starter Playbook uses `navigate_to_plans`; add one line to `options`:
```tsx docs-check=false reason="one option line; the full initRevTurbine options object is shown above"
uiPathResolvers: { navigate_to_plans: () => { window.location.href = '/pricing'; } },
```
Every action type and what it carries: [Placements → UI paths](/docs/guides/placements/).
## 6. Report usage and track events
Usage limits and credits need to know the balance; the Playbook decides what to do about it.
```ts
await rt.update({ usage: { ai_credits_used: 42 } }); // absolute balance, not a delta
await rt.track('ai_generation_completed', { credits: 3 });
```
Balances drive quota meters, low-credit warnings and limit prompts. Events feed targeting and analytics.
## 7. Verify on your server
The browser check drives the UI; your server makes the money-safe decision before paid work runs — with the same SDK, evaluating the same Playbook in-process.
```ts
import { initRevTurbine, RuntimeMode } from '@revturbine/sdk/headless';
async function handleExport(req, res) {
const rt = await initRevTurbine({
tenantId: process.env.REVTURBINE_TENANT_ID!,
apiKey: process.env.REVTURBINE_API_KEY!,
endpoint: process.env.REVTURBINE_ENDPOINT!,
runtimeMode: RuntimeMode.Server,
user: { id: req.userId, plan_handle: req.user.planHandle },
});
const result = await rt.can('batch_export');
if (!result.allowed) return res.status(402).json({ error: 'upgrade_required' });
await runExport(req);
}
```
Why the re-check, and which actions need it: [Client vs Server Enforcement](/docs/concepts/enforcement/).
## 8. Go hosted: connect Stripe, change it without a deploy
Everything above works forever in local mode, free. Hosted mode is where you stop deploying to change monetization.
1. **Create an account and connect the CLI.** [revturbine.com/app](https://revturbine.com/app), then `revturbine login` (a browser device flow — no keys to paste).
2. **Connect Stripe** in **Settings → Stripe**. From then on a purchase updates what the user can do with no app code: plan and payment state arrive through a client session. See [Billing & Stripe](/docs/guides/billing/).
3. **Switch the provider to hosted mode.** Mint a publishable ingest key under **Settings → API tokens**, then:
```tsx
const options = useMemo(() => ({
tenantId: 'tenant_abc',
publicKey: 'rtk_…', // public key — safe in the browser
user: { id: currentUser.id }, // plan_handle now comes from Stripe
clientSession: () => fetch('/api/revturbine-session', { method: 'POST' }).then((r) => r.json()).then((j) => j.client_token),
}), [currentUser.id]);
```
4. **Change something and reload.** Open the dashboard, edit the banner's copy or the free plan's export limit, launch the release. The SDK picks it up on its next refresh — no deploy. Or do it from the terminal: `revturbine download --live`, edit, `revturbine validate`, `revturbine diff --live`, `revturbine launch`.
That loop — see what converted, change the Playbook, launch — is the product. Your agent can drive it too: [Add RevTurbine with a Coding Agent](/docs/guides/coding-agent/).
## Why RevTurbine
**Decisions, not just rendering**
Caps, cooldowns, eligibility, and priority across competing placements are decided for you. One `` returns the winner. No client-side orchestration, no homegrown segment logic.
**Entitlements and prompts in one engine, tied to billing**
A `` surfaces usage warnings, trial nudges and lifecycle prompts tied to billing state; `rt.can()` confirms or denies access. Gate a feature and show the upgrade prompt that converts it, from one Playbook.
**Changed by product, not by a deploy**
Drop a slot once. Copy, targeting, limits, offers and prices change in the dashboard or through the CLI; resolvers keep navigation in your code. Every launch is a versioned release you can roll back.
**The same answer everywhere**
`@revturbine/sdk/headless` imports no React; Python and Rust evaluate the same Playbook. A cross-language gate diffs their decisions byte-for-byte, so a check answers identically wherever you make it.
## Integration reference
| Task | Guide |
|---|---|
| Initialize React and inspect readiness | [React Integration](/docs/getting-started/react/#userevturbine) |
| Run checks without React | [TypeScript / Node](/docs/guides/headless-api/), [Python](/docs/getting-started/python/), [Rust](/docs/getting-started/rust/) |
| Choose Playbook delivery and optional wiring | [Runtime Modes](/docs/guides/runtime-modes/), [Configuration](/docs/reference/configuration/) |
| Match your app's colors | [Theming](/docs/guides/theming/) |
| Route blocked actions to a paywall | [Paywall Host Pattern](/docs/guides/paywall-host/) |
| Explain a missing placement or denied check | [Troubleshooting](/docs/guides/troubleshooting/), [Debugging Decisions](/docs/operate/debugging/) |
| Check release changes before upgrading | [SDK changelog](https://github.com/revt-eng/revturbine-sdk/blob/main/CHANGELOG.md) |
Coding agents can fetch individual pages from this index. [llms-small.txt](/docs/llms-small.txt) is an abridged documentation bundle, including the language guides; [llms-full.txt](/docs/llms-full.txt) contains the complete documentation.
## Next steps
- [Recommended API Path](/docs/getting-started/api-overview/) — `can()` vs `gate()` vs ``, and the vocabulary
- [Entitlements](/docs/guides/entitlements/) — feature gates, usage limits, credits, seats, tiers
- [Placements](/docs/guides/placements/) — targeting, caps, cooldowns, priority, and UI paths
- [Runtime Modes](/docs/guides/runtime-modes/) — local mode, hosted mode, and what is cached
- [Interactive Playground](/docs/playground/) — every scenario, live and editable
- [API reference](/docs/api/) — full TypeDoc reference for all exports
# BrowserStorage
Wraps a browser `Storage` object (`localStorage` or `sessionStorage`)
with try/catch guards for quota and security-policy errors.
## Implements
- `RevTurbineStorage`
# CtaResolverRegistry
Registry mapping CTA action types to resolver functions.
Customers register a [CtaResolver](/docs/api/type-aliases/ctaresolver/) against an action type — typically a
tenant-defined custom action name surfaced through `parseUiPath` — so the
[PlacementRenderer](/docs/api/functions/placementrenderer/) can dispatch a CTA click to dedicated logic instead
of the generic `onCtaClick` callback. Built-in action types may also be
overridden by registering a resolver for them.
## Methods
### clear()
> **clear**(): `void`
Remove every registered resolver.
#### Returns
`void`
***
### get()
> **get**(`type`): [`CtaResolver`](/docs/api/type-aliases/ctaresolver/) \| `undefined`
Look up the resolver registered for an action type, if any.
#### Parameters
##### type
`string`
#### Returns
[`CtaResolver`](/docs/api/type-aliases/ctaresolver/) \| `undefined`
***
### has()
> **has**(`type`): `boolean`
Whether a resolver is registered for an action type.
#### Parameters
##### type
`string`
#### Returns
`boolean`
***
### register()
> **register**(`type`, `resolver`): `void`
Register a resolver for an action type. Re-registering the same type
replaces the previous resolver (and warns), matching the slot-type
registry's override semantics.
#### Parameters
##### type
`string`
##### resolver
[`CtaResolver`](/docs/api/type-aliases/ctaresolver/)
#### Returns
`void`
***
### unregister()
> **unregister**(`type`): `boolean`
Remove the resolver for an action type.
Returns true if a resolver was registered and removed.
#### Parameters
##### type
`string`
#### Returns
`boolean`
# PlacementController
Framework-agnostic placement lifecycle controller.
Encapsulates the same register → decide → impression-track → interact flow
that the React `usePlacement()` hook provides, but without any framework dependency.
Subscribe to state changes with [onChange](/docs/api/classes/placementcontroller/#onchange) and read the current state
with [state](/docs/api/classes/placementcontroller/#state), or just `await load()` for a one-shot pattern.
## Example
```ts
const ctrl = new PlacementController(sdk, { surfaceSlot: { id: 'upsell_banner' } });
ctrl.onChange(() => updateUI(ctrl.state));
await ctrl.load();
```
## Accessors
### content
#### Get Signature
> **get** **content**(): `RevTurbineDecisionContent` \| `null`
Current resolved content, or `null` if no decision has been loaded.
##### Returns
`RevTurbineDecisionContent` \| `null`
***
### decision
#### Get Signature
> **get** **decision**(): `RevTurbinePlacementDecision` \| `null`
Current decision, or `null` if not yet loaded.
##### Returns
`RevTurbinePlacementDecision` \| `null`
***
### placementId
#### Get Signature
> **get** **placementId**(): `string`
Registered placement ID, or empty string before first `load()`.
##### Returns
`string`
***
### state
#### Get Signature
> **get** **state**(): [`PlacementControllerState`](/docs/api/interfaces/placementcontrollerstate/)
Current state snapshot.
##### Returns
[`PlacementControllerState`](/docs/api/interfaces/placementcontrollerstate/)
***
### visible
#### Get Signature
> **get** **visible**(): `boolean`
Convenience: `true` when the current decision says the placement is visible.
##### Returns
`boolean`
## Methods
### ctaClick()
> **ctaClick**(`ctaTarget?`): `Promise`\<`void`\>
Record a CTA click interaction.
#### Parameters
##### ctaTarget?
`string`
#### Returns
`Promise`\<`void`\>
***
### ctaComplete()
> **ctaComplete**(`ctaTarget?`): `Promise`\<`void`\>
Record a CTA completion interaction and hide the placement.
#### Parameters
##### ctaTarget?
`string`
#### Returns
`Promise`\<`void`\>
***
### dismiss()
> **dismiss**(`cooldownMs?`): `Promise`\<`void`\>
Record a dismissal.
#### Parameters
##### cooldownMs?
`number`
Optional explicit window. Omit it — which is the normal
case — and the SDK resolves the payload's authored `caps.cooldown_days`,
falling back to the 7-day default. This used to default to 24h here and
pass it ALWAYS, so the authored value and the 7-day default were both
unreachable from React (plan 233 TASK-8b).
#### Returns
`Promise`\<`void`\>
***
### dispose()
> **dispose**(): `void`
Stop listening for Playbook loads and user-context changes. Idempotent; call
it when the owning component unmounts (`usePlacement` does).
#### Returns
`void`
***
### load()
> **load**(): `Promise`\<`RevTurbinePlacementDecision` \| `null`\>
Register the placement (if needed) and fetch a decision.
When `autoTrackImpression` is true (default), a visible decision
automatically records an impression event.
#### Returns
`Promise`\<`RevTurbinePlacementDecision` \| `null`\>
***
### markRendered()
> **markRendered**(): `void`
Called when the placement's visual root renders (plan 144 TASK-11). Emits
`placement_rendered` once, and under `placementExposure: 'render'` credits
the presentation now. Idempotent.
#### Returns
`void`
***
### markVisible()
> **markVisible**(`basis?`): `void`
Called by the viewport-exposure substrate when the placement's visual root
first enters the viewport — or immediately with `'render_fallback'` when
`IntersectionObserver` is unavailable (plan 144 TASK-9/11 / AC-9, AC-10).
Emits `placement_exposed` once with the basis, and under
`placementExposure: 'viewport'` a viewport exposure credits the presentation
here (Q-8). Idempotent.
#### Parameters
##### basis?
`ExposureBasis` = `'viewport'`
how exposure was established; defaults to `'viewport'`
#### Returns
`void`
***
### onChange()
> **onChange**(`listener`): () => `void`
Subscribe to state changes. Returns an unsubscribe function.
#### Parameters
##### listener
[`ChangeListener`](/docs/api/type-aliases/changelistener/)
#### Returns
() => `void`
#### Example
```ts
const unsub = ctrl.onChange(() => console.log(ctrl.state));
// later...
unsub();
```
***
### refresh()
> **refresh**(): `Promise`\<`RevTurbinePlacementDecision` \| `null`\>
Re-fetch the placement decision (clears impression + render + exposure tracking).
#### Returns
`Promise`\<`RevTurbinePlacementDecision` \| `null`\>
***
### remindMeLater()
> **remindMeLater**(`seconds?`): `Promise`\<`void`\>
Alias for [snooze](/docs/api/classes/placementcontroller/#snooze).
#### Parameters
##### seconds?
`number`
#### Returns
`Promise`\<`void`\>
***
### reset()
> **reset**(): `void`
Reset the controller state (clears decision, placement ID, etc.).
#### Returns
`void`
***
### snooze()
> **snooze**(`seconds?`): `Promise`\<`void`\>
Record a snooze / remind-me-later.
#### Parameters
##### seconds?
`number`
Optional explicit window. Omit it and the SDK resolves the
payload's authored `remind_later_minutes`, falling back to the tenant
default (plan 233 TASK-8c). Remind-later is a DIFFERENT window from
dismiss and resolves independently.
#### Returns
`Promise`\<`void`\>
***
### watchUserContext()
> **watchUserContext**(): () => `void`
Re-decide whenever the user context changes (BL-0004).
The counterpart to EntitlementGate.watchUserContext, and the reason
`sdk.convert()` now removes a converted placement in place: conversion writes
no suppression — eligibility is a function of the user's CURRENT plan and
targeting — so the only thing that can take a converted upgrade banner off
screen is a re-decide against the reloaded context. Without this the
placement stayed mounted until something else happened to re-resolve it, and
the host had to call `refresh()` itself or use the component's
`ctaComplete()` (the limitation plan 236 TASK-13 recorded).
Only re-decides a controller that has already resolved something: before the
first `load()` there is nothing on screen to be stale, and firing then would
turn every startup `identify()` into a duplicate decision.
Calls `load()` rather than `refresh()` on purpose — `refresh()` clears
impression/exposure tracking, so a context change would re-credit a
presentation the user never saw again.
Returns an unsubscribe function; [dispose](/docs/api/classes/placementcontroller/#dispose) also drops it.
#### Returns
() => `void`
# PlacementTypeRegistry
Registry for placement slot types.
Manages built-in and custom placement renderers. The registry resolves
a PlacementOutput to the best matching slot type based on surface type
and template, then provides the component and resolved props for rendering.
## Methods
### get()
> **get**(`id`): [`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\> \| `undefined`
Look up a slot type by its id.
#### Parameters
##### id
`string`
#### Returns
[`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\> \| `undefined`
***
### has()
> **has**(`id`): `boolean`
Check if a slot type id is registered.
#### Parameters
##### id
`string`
#### Returns
`boolean`
***
### listAll()
> **listAll**(): [`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
List all registered slot types.
#### Returns
[`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
***
### listByComponentType()
> **listByComponentType**(`componentType`): [`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
List slot types for a specific surface type.
#### Parameters
##### componentType
`"inline"` \| `"custom"` \| `"push"` \| `"banner"` \| `"modal"` \| `"tooltip"` \| `"sidebar"` \| `"toast"` \| `"fullscreen"` \| `"email"` \| `"sms"` \| `"in_page"` \| `"button"` \| `"full_page"` \| `"agent"` \| `"cli"`
#### Returns
[`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
***
### ~~listBySurfaceType()~~
> **listBySurfaceType**(`componentType`): [`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
:::caution[Deprecated]
Use [listByComponentType](/docs/api/classes/placementtyperegistry/#listbycomponenttype).
:::
#### Parameters
##### componentType
`"inline"` \| `"custom"` \| `"push"` \| `"banner"` \| `"modal"` \| `"tooltip"` \| `"sidebar"` \| `"toast"` \| `"fullscreen"` \| `"email"` \| `"sms"` \| `"in_page"` \| `"button"` \| `"full_page"` \| `"agent"` \| `"cli"`
#### Returns
[`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\>[]
***
### register()
> **register**\<`P`\>(`options`): `void`
Register a placement slot type. If a type with the same id already exists,
it is replaced (allows customer overrides of built-in types).
#### Type Parameters
##### P
`P` *extends* [`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>
#### Parameters
##### options
[`RegisterPlacementSlotTypeOptions`](/docs/api/type-aliases/registerplacementslottypeoptions/)\<`P`\>
#### Returns
`void`
***
### resolve()
> **resolve**(`output`): [`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\> \| `undefined`
Resolve the best matching slot type for a placement output.
Resolution order:
1. If the output's surface.template matches a registered type id, use it
2. Find all types for the output's surface.type, sort by priority descending,
and pick the first whose `accepts()` returns true
3. Fall back to a 'custom' type if registered
4. Return undefined if no match
#### Parameters
##### output
#### Returns
[`PlacementSlotType`](/docs/api/interfaces/placementslottype/)\<[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)\<[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)\>\> \| `undefined`
***
### unregister()
> **unregister**(`id`): `boolean`
Unregister a placement slot type by id.
Returns true if the type was found and removed.
#### Parameters
##### id
`string`
#### Returns
`boolean`
# RevTurbineApiError
Error thrown when an API request fails.
## Extends
- `Error`
# RevTurbineCustomerSdk
The core RevTurbine customer-facing SDK.
Provides methods for:
- **Identity** — `identify()`, `resetIdentity()`
- **Placements** — `registerPlacement()`, `getPlacementDecision()`, `getPlacement()`
- **Entitlements** — `checkEntitlement()`, `updateUsage()`
- **Trials** — `getTrialStatus()`
- **Events** — `track()` / `capture()` (generic lane; platform-name
collisions are namespaced), `emitPlatformEvent()` (typed platform lane,
contract-validated payloads)
- **Interactions** — `trackTreatmentInteraction()`, `dismiss()`, `convert()`
- **Context** — `setUserContext()`, `setPageContext()`, `refreshPageContext()`
## Example
```ts
import { initRevTurbine } from '@revturbine/sdk';
const session = await initRevTurbine({
tenantId: 'tenant_abc',
apiKey: 'rt_live_xxx',
endpoint: 'https://edge.example.com',
mode: 'react',
});
session.identify('user_123', { plan_handle: 'pro' });
const branding = session.sdk.getBranding();
```
## Methods
### can()
> **can**(`handle`, `context?`): `Promise`\<\{ \}\>
Check whether the user can do something — the advertised alias of
[checkEntitlement](/docs/api/classes/revturbinecustomersdk/#checkentitlement). Returns the rich EntitlementResult
(`allowed`, `status`, `reason`, and `limit`/`used`/`remaining` for
limit-bearing entitlements). This client check is a UX convenience — for
billing-critical or abuse-sensitive actions, re-run the same check on your
server before granting access.
#### Parameters
##### handle
`string`
##### context?
`RevTurbineEntitlementContext`
#### Returns
`Promise`\<\{ \}\>
#### Example
```ts
const access = await rt.can('generate_image');
if (!access.allowed) showUpgrade();
@public
```
***
### checkEntitlement()
> **checkEntitlement**(`handle`, `context?`): `Promise`\<\{ \}\>
Evaluate a single entitlement handle for the active user, purely from the
loaded UserContext and Playbook. The advertised [can](/docs/api/classes/revturbinecustomersdk/#can) alias wraps
this with the friendly verb name.
A check that lands before the Playbook does waits out the in-flight load,
bounded, and re-evaluates (BL-0179); it fails CLOSED only once nothing more
is coming. See [RevTurbinePlaybookLoadState](/docs/api/type-aliases/revturbineplaybookloadstate/).
#### Parameters
##### handle
`string`
##### context?
`RevTurbineEntitlementContext`
#### Returns
`Promise`\<\{ \}\>
***
### checkoutMetadata()
> **checkoutMetadata**(`outputId`): [`RevTurbineCheckoutMetadata`](/docs/api/interfaces/revturbinecheckoutmetadata/) \| `null`
The Stripe Checkout `revturbine_*` metadata bag for a rendered output
(plan 282 REQ-5; research §6.2), keyed by the `output_id` of a decision
this SDK produced — what the customer's server passes when it creates the
Checkout Session the output's CTA leads to.
Pass `metadata` as the session's `metadata` AND as
`subscription_data.metadata`. The session copy is what
`checkout.session.completed` carries; the subscription copy is what every
later `customer.subscription.*` event carries and what each invoice
snapshots under `parent.subscription_details.metadata`, so renewals,
failures and recoveries stay attributable without a `customers` row. Pass
`client_reference_id` as the session's `client_reference_id`, Stripe's own
caller-side key, which the identity bridge also reads as the user id.
Checkout cannot set Customer metadata inline: if you let Checkout create
the Customer, follow up with `customers.update({ metadata })` — see the
billing guide.
Every value is one this SDK holds; nothing is minted. The user id is the
one `identify()` set, as this SDK's own telemetry carries it (an
email-shaped id is hashed the same way on every lane, so the purchase
joins the click). The account id is present only when an account was
identified — never the user-derived fallback. A key the SDK does not hold
is absent, not empty.
#### Parameters
##### outputId
`string`
#### Returns
[`RevTurbineCheckoutMetadata`](/docs/api/interfaces/revturbinecheckoutmetadata/) \| `null`
`null` when no decision from this SDK produced `outputId`.
***
### convert()
> **convert**(`outputId`): `Promise`\<`void`\>
Record a confirmed CTA completion, then reload the UserContext so every
mounted surface re-decides against the plan the user just converted to.
Conversion is **not** a suppression: it writes no cooldown and no permanent
retirement (spec §"interaction" table; Kent's plan 254 D-9 ruling). What
makes a converted upgrade banner leave the screen is the user no longer
matching it — so a conversion that did not refresh the context left the
placement mounted until something else happened to re-resolve it. Kent,
2026-09-25 (BL-0004): *"Converting should trigger a reloading of UserContext
with the new plan/billing state the user has converted to."*
Ordering, and why it is this way round:
1. The interaction is recorded (analytics + attribution), as before.
2. **Optimistically**, locally: when the converted output's CTA names a
target plan (`cta_path.plan_handle`), the held context moves to that plan
immediately. Billing truth arrives by Stripe webhook, so
`GET /api/sdk/client-context` can still report the OLD plan for seconds
after a successful checkout — waiting for it would leave the upgrade
prompt on screen exactly when the user has just paid.
3. Subscribers are notified either way — a conversion whose CTA names no
plan still re-decides mounted slots and re-checks mounted gates, which is
what turns a plan change the host applied itself into a re-render.
4. **Then** the server refresh (`fetchClientContext()`), which overlays the
RevTurbine-authoritative trial / billing-health / plan fields and
notifies again if it changed anything. It is best-effort and never
throws, so awaiting it cannot fail a conversion; with no client-session
minter configured it is a no-op and the optimistic state stands.
Mounted `PlacementController`s re-decide and mounted `EntitlementGate`s
re-check through their `watchUserContext()` subscriptions — which the React
bindings attach for you. Re-resolution emits the normal lifecycle events
once per final decision; the transient states emit nothing.
`PlacementController.ctaComplete()` is unchanged and remains the
component-level path.
#### Parameters
##### outputId
`string`
#### Returns
`Promise`\<`void`\>
***
### diagnoseSlotInventory()
> **diagnoseSlotInventory**(): [`RevTurbineSlotInventoryDiagnosis`](/docs/api/interfaces/revturbineslotinventorydiagnosis/)
Diff the authored Playbook against what this app actually mounted, in both
directions.
Plan 233 TASK-10 / AC-12. Both directions matter and they fail differently:
- **authoredButUnmounted** — a placement targets a slot no code renders. It
can never show. Nothing reports this today; the decision path is never
even asked about a slot that was never mounted, so there is no error, no
warning, and no reason code. This is the silent one.
- **mountedButUnauthored** — a slot is mounted that no placement targets. It
renders its fallback forever, which usually means a typo in the slot id
or config that was never deployed.
This is a diagnostic and deliberately reports rather than throws: it is run
against a live app, and an integration with a stale slot should keep
working while someone reads the output.
A config-side audit **cannot** produce this. That is the lesson from the
August diagnosis of the escalated integration, which concluded the fixed
slots were unmounted: they were mounted, and the call sites passed slot ids
that no placement targeted. Only the running app knows what it mounted.
#### Returns
[`RevTurbineSlotInventoryDiagnosis`](/docs/api/interfaces/revturbineslotinventorydiagnosis/)
***
### dismiss()
> **dismiss**(`outputId`): `Promise`\<`void`\>
Suppress a decision's placement for its authored dismiss cooldown.
Terminal for the window: `getPlacementDecision` will not return it again
until the cooldown lapses.
#### Parameters
##### outputId
`string`
#### Returns
`Promise`\<`void`\>
***
### dispose()
> **dispose**(): `void`
Stop background clickstream flushing and release page-unload listeners.
Flushes any buffered events one last time. Call when tearing down an SDK
instance (e.g. SPA unmount) to avoid a dangling interval/listeners.
#### Returns
`void`
***
### emitPlatformEvent()
> **emitPlatformEvent**\<`K`\>(`eventName`, `payload`, `options?`): `Promise`\<`void`\>
Emit a typed platform event (plan 228 TASK-4).
The sanctioned first-party path for every platform event OUTSIDE the
control plane — SDK lifecycle, gates, placements, milestones, growth
signals, and the R-2 billing vocabulary (a billing fact consumed from
Stripe is equally emittable here; the ingest path dedupes on the
contract's identity keys, e.g. `billing_ref`). The payload parameter type
is inferred from the event's payload contract, so the API cannot drift
from the schema the payload is validated against.
The name is sent RAW — no `clickstream_*` namespacing — which is exactly
what distinguishes this from [track](/docs/api/classes/revturbinecustomersdk/#track): the generic lane namespaces
platform collisions, the typed lane IS the platform.
In development/test builds the payload is validated against its contract
at emit time; a violation emits an `sdk_validation_warning` alongside
(never a throw — telemetry must not break the host app). Production
builds skip validation; the ingest boundary re-validates authoritatively.
#### Type Parameters
##### K
`K` *extends* [`EmittablePlatformEventName`](/docs/api/type-aliases/emittableplatformeventname/)
#### Parameters
##### eventName
`K`
##### payload
`EventPayloadInput`\<`K`\>
##### options?
`RevTurbineEventOptions`
#### Returns
`Promise`\<`void`\>
***
### emitSemantic()
> **emitSemantic**(`eventType`, `payload`, `options?`): `Promise`\<`void`\>
Emit a semantic event through the GENERIC string lane.
Since plan 228 TASK-4 this is a customer-lane API, not a platform one: it
rides capture, so a name colliding with platform vocabulary is
namespaced `clickstream_*` — platform events cannot be forged from here.
The SDK's own platform emissions use [emitPlatformEvent](/docs/api/classes/revturbinecustomersdk/#emitplatformevent), whose
payloads are typed and contract-validated.
The fields are passed to capture DIRECTLY, exactly as a customer's
own `capture(name, {...})` call would. Anything else double-wraps them.
This used to send `{semantic: true, payload: fields}`. The wire mapping
already stores an envelope's properties under the key `payload`, so that
inner wrapper landed the fields at `properties.payload.payload.` in
`events_clickstream` — one level deeper than every `capture()` event and
than the canonical carrier this SDK documents. Readers that followed the
documented shape (`user_context_fields`, any analytics pipe) found nothing
on a semantic event, which is why gate telemetry was invisible to the
entitlement metrics despite being emitted correctly.
The `semantic: true` marker went with it: it was written here and read
nowhere in the SDK, the web app, or any pipe. It marked nothing and cost a
whole nesting level.
`liftClickstreamFields` / `pickClickstreamField` are unaffected — they
resolve top-level first and fall back to the nested bag, so they read the
fields either way. Only the STORED JSON changes shape, and it changes to
the shape everything else already expects.
#### Parameters
##### eventType
`string`
##### payload
[`SdkEventProperties`](/docs/api/type-aliases/sdkeventproperties/)
##### options?
`RevTurbineEventOptions`
#### Returns
`Promise`\<`void`\>
***
### emitTrigger()
> **emitTrigger**(`trigger`, `payload?`, `options?`): `Promise`\<`void`\>
Emit a canonical trigger event. There is no in-SDK consumer that matches
on trigger names — the shipped behavior is ingest to `events_clickstream`
only (plan 250's wire/consumer matrix); a decision-engine placement
trigger matches `trigger.slot_id` against registered slots, not this
event's name.
Convenience wrapper over [emitSemantic](/docs/api/classes/revturbinecustomersdk/#emitsemantic) that constrains the event
name to [RevTurbineTriggerEvent](/docs/api/type-aliases/revturbinetriggerevent/) and attaches standard context
(user_id, plan, timestamp) automatically.
**Three trigger names land namespaced on the wire.** This rides the
generic emit lane, which prefixes `clickstream_` to any name the platform
taxonomy already declares — the rule that stops a client forging a
first-party fact by naming an event after one. `trial_expired`,
`payment_failed` and `feature_gated` collide, so they are stored as
`clickstream_trial_expired`, `clickstream_payment_failed` and
`clickstream_feature_gated`. Query those names, not the raw ones. The
remaining triggers are unaffected, and the platform's own versions of the
three are emitted first-party through the typed surface. Pinned by
`trigger-namespacing-parity.test.ts` (plan 231 REQ-4).
#### Parameters
##### trigger
`"payment_failed"` \| `"feature_gated"` \| `"trial_expired"` \| `"trial_midpoint"` \| `"trial_expiring"` \| `"usage_limit_approaching"` \| `"usage_limit_reached"` \| `"credit_balance_low"` \| `"seat_limit_reached"` \| `"cancel_intent"` \| `"auto_renewal_reminder"` \| `"onboarding_complete"` \| `"invite_teammate_prompt"` \| `"referral_offer"` \| `"plan_upgrade_nudge"`
##### payload?
[`RevTurbineTriggerPayload`](/docs/api/type-aliases/revturbinetriggerpayload/)
##### options?
`RevTurbineEventOptions`
#### Returns
`Promise`\<`void`\>
#### Example
```ts
await sdk.emitTrigger('usage_limit_approaching', { usage_percent: 85, threshold: 80 });
await sdk.emitTrigger('trial_expiring', { days_remaining: 2 });
// stored as `clickstream_feature_gated` — see the collision note above
await sdk.emitTrigger('feature_gated', { feature: 'advanced_automation' });
```
***
### explainPlacementDecision()
> **explainPlacementDecision**(`input`): `Promise`\<[`RevTurbinePlacementDecisionExplanation`](/docs/api/interfaces/revturbineplacementdecisionexplanation/)\>
Explain why a placement decision was selected.
Returns a structured diagnostic payload that includes:
- Segment membership evaluation with predicate-level pass/fail details
- Entitlement-rule matching outcomes for current targeting context
- Placement decision metadata (`rule_id`, reason codes, suppression)
Useful for debuggers, QA tooling, and customer-facing explainability UIs.
#### Parameters
##### input
`RevTurbinePlacementDecisionInput`
#### Returns
`Promise`\<[`RevTurbinePlacementDecisionExplanation`](/docs/api/interfaces/revturbineplacementdecisionexplanation/)\>
***
### fetchClientContext()
> **fetchClientContext**(`clientToken?`): `Promise`\<`void`\>
Enrich the held UserContext with the user's server-known CLIENT-SAFE context
(plan 157). Fetches `GET /api/sdk/client-context` with the provided client
token (`rt_client_`, minted by the customer's backend via the server SDK's
`clientSessions.create`), maps the client-safe fields (trial state, coarse
billing health) into the UserContext, and merges them in — these fields are
RevTurbine-authoritative, so they refresh the held context.
The token is held in memory only (never storage / URL / logs). Enrichment is
best-effort: a network error or non-200 never throws into the app; on 401
(expired / revoked) the held token is cleared. Server-side entitlement checks
remain authoritative regardless of what the browser sees.
#### Parameters
##### clientToken?
`string`
the `rt_client_` token; when omitted, reuses the last one.
#### Returns
`Promise`\<`void`\>
***
### fetchUserContext()
> **fetchUserContext**(`userId`): `Promise`\<`UserTargetingContext`\>
Fetch the resolved user context from the decision API.
Returns the user's matched segments, traits, plan, and usage — used
to determine which placement payloads are eligible for display.
#### Parameters
##### userId
`string`
#### Returns
`Promise`\<`UserTargetingContext`\>
***
### flushEvents()
> **flushEvents**(): `Promise`\<`void`\>
Flush any buffered clickstream events to `POST /api/track` immediately.
Called on the size threshold, the [RevTurbineEventBatchingOptions](/docs/api/interfaces/revturbineeventbatchingoptions/)
interval timer, and page-unload. Best-effort and non-throwing — delivery
failures are swallowed (plan 95 REQ-3). Safe to call when the buffer is
empty (no-op).
#### Returns
`Promise`\<`void`\>
***
### gate()
> **gate**\<`T`\>(`action`, `fn`, `context?`): `Promise`\<[`RevTurbineGateResult`](/docs/api/type-aliases/revturbinegateresult/)\<`T`\>\>
Gate an action behind an entitlement — the advertised `gate(action, fn)` verb.
Checks the entitlement for `action`; if allowed, runs `fn` and returns its
result; otherwise does NOT run `fn` and returns the entitlement so the caller
can surface a paywall (e.g. render an ``). See [RevTurbineGateResult](/docs/api/type-aliases/revturbinegateresult/).
#### Type Parameters
##### T
`T`
#### Parameters
##### action
`string`
##### fn
() => `T` \| `Promise`\<`T`\>
##### context?
`RevTurbineEntitlementContext`
#### Returns
`Promise`\<[`RevTurbineGateResult`](/docs/api/type-aliases/revturbinegateresult/)\<`T`\>\>
#### Example
```ts
const gated = await rt.gate('export_pdf', () => exportPdf());
if (!gated.ran) openPaywall(gated.entitlement);
@public
```
***
### getBranding()
> **getBranding**(): [`ResolvedBranding`](/docs/api/interfaces/resolvedbranding/)
Resolve the branding to render with, down the four-rung ladder (plan 118):
the explicit `branding` init option → the config's deprecated embedded
`theme` (dev-warns) → the `apiBranding` init option → [DEFAULT\_BRANDING](/docs/api/variables/default_branding/).
Branding is a display concern only — it never affects placement decisions or
entitlement checks. The result is always complete, so callers can render
unconditionally even when no branding was supplied.
#### Returns
[`ResolvedBranding`](/docs/api/interfaces/resolvedbranding/)
The merged branding and the ladder rung it came from.
***
### getEffectiveUserContext()
> **getEffectiveUserContext**(): `Promise`\<`Readonly`\<[`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)\>\>
Return the immutable ephemeral context currently entering local evaluation.
#### Returns
`Promise`\<`Readonly`\<[`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)\>\>
***
### getEligibleAddons()
> **getEligibleAddons**(): `Promise`\<[`EligibleAddon`](/docs/api/interfaces/eligibleaddon/)[]\>
Return the public add-on variations eligible for the active user.
#### Returns
`Promise`\<[`EligibleAddon`](/docs/api/interfaces/eligibleaddon/)[]\>
***
### getEligiblePlans()
> **getEligiblePlans**(): `Promise`\<[`EligiblePlan`](/docs/api/interfaces/eligibleplan/)[]\>
Return the public plan variations eligible for the active user.
#### Returns
`Promise`\<[`EligiblePlan`](/docs/api/interfaces/eligibleplan/)[]\>
***
### getEntitlements()
> **getEntitlements**(): `Record`\<`string`, `EntitlementResult`\>
Return the SDK-resolved entitlement snapshot for the active user.
The returned object is keyed by entitlement handle and reflects the
latest results from local/runtime checks tracked by the SDK.
#### Returns
`Record`\<`string`, `EntitlementResult`\>
***
### getExperimentVariant()
> **getExperimentVariant**(`experimentHandle`): `Promise`\<`ExperimentVariantSelection`\>
Resolve the normalized variant used by RevTurbine decisions and telemetry.
Missing, conflicting, and unavailable assignments remain explicit and are
never converted to a control variant.
#### Parameters
##### experimentHandle
`string`
#### Returns
`Promise`\<`ExperimentVariantSelection`\>
***
### ~~getExportedConfig()~~
> **getExportedConfig**(): \{ \} \| `undefined`
:::caution[Deprecated]
Renamed to [RevTurbineCustomerSdk.getPlaybook](/docs/api/classes/revturbinecustomersdk/#getplaybook) —
`Playbook` is the canonical name for the artifact (BL-0156). Identical
behaviour; removed in `0.12.0`.
:::
#### Returns
\{ \} \| `undefined`
***
### getPersonalizationTokens()
> **getPersonalizationTokens**(`payload?`): `RevTurbinePersonalizationTokens`
Return the SDK-resolved personalization token map.
Reads from the transient `personalization` map on the user context,
which holds both SDK-derived tokens (plan_name, usage_current, etc.)
and app-provided tokens set via `setPersonalization()` or `identify()`.
Pass the placement's payload (its `recommendation_strategy` /
`recommendation_plan_override` fields) to resolve the
`{{recommended_plan_handle}}` / `{{recommended_plan_name}}` tokens for
that specific placement (plan #47, Appendix C.3). Without a payload the
map carries the user-level `next_tier_up` default; with one, the
placement's authored strategy (e.g. a `custom` forced plan) overlays
those two tokens. All other tokens are unaffected.
#### Parameters
##### payload?
Optional placement payload carrying the per-placement
recommendation strategy. Omit for the user-level default.
###### recommendation_plan_override?
`string` \| `null`
###### recommendation_strategy?
`RecommendationStrategy` \| `null`
#### Returns
`RevTurbinePersonalizationTokens`
***
### getPlacement()
> **getPlacement**(`config`): `Promise`\<\{ \} \| `null`\>
Resolve the legacy placement payload for a request config. Prefer
[getPlacementDecision](/docs/api/classes/revturbinecustomersdk/#getplacementdecision) for new integrations; kept for the
documented legacy request-config path.
#### Parameters
##### config
`RevTurbinePlacementRequestConfig`
#### Returns
`Promise`\<\{ \} \| `null`\>
***
### getPlacementDecision()
> **getPlacementDecision**(`input`): `Promise`\<`RevTurbinePlacementDecision`\>
Resolve the placement decision for a registered surface slot — the
evaluated payload (or lack of one), its reason, and the metadata needed
to report the interaction lifecycle ([trackTreatmentInteraction](/docs/api/classes/revturbinecustomersdk/#tracktreatmentinteraction),
[dismiss](/docs/api/classes/revturbinecustomersdk/#dismiss), [snooze](/docs/api/classes/revturbinecustomersdk/#snooze), [convert](/docs/api/classes/revturbinecustomersdk/#convert)).
#### Parameters
##### input
`RevTurbinePlacementDecisionInput`
#### Returns
`Promise`\<`RevTurbinePlacementDecision`\>
***
### getPlaybook()
> **getPlaybook**(): \{ \} \| `undefined`
Returns the Playbook snapshot loaded at initialization.
Canonical consumers should retain their own Playbook or use
[normalizeConfigArtifactOrThrow](/docs/api/functions/normalizeconfigartifactorthrow/) at their ingestion boundary rather
than reading it back out of the SDK.
#### Returns
\{ \} \| `undefined`
***
### getPolicy()
> **getPolicy**(): [`RevTurbinePolicySnapshot`](/docs/api/interfaces/revturbinepolicysnapshot/)
Return current SDK policy snapshot.
#### Returns
[`RevTurbinePolicySnapshot`](/docs/api/interfaces/revturbinepolicysnapshot/)
***
### getRegisteredSlots()
> **getRegisteredSlots**(): [`RevTurbineRegisteredSlot`](/docs/api/interfaces/revturbineregisteredslot/)[]
Every surface slot this SDK instance currently has mounted.
Plan 233 TASK-10. `verify-integration` names the gap this closes in its own
text: *"A placement targeting a slot no code renders can never show, and
nothing reports this; there is no probe, so walk the Playbook's placement
triggers against your inventory of rendered slots."* There was no way to
obtain that inventory at runtime — the registry is private and keyed by a
hashed id, not by the slot id the author wrote.
`slotId` is the author-facing identity (what a Playbook's
`trigger.slot_id` must match), not the internal hash. That distinction is
the whole point: a diff against authored config is meaningless in hash
space.
#### Returns
[`RevTurbineRegisteredSlot`](/docs/api/interfaces/revturbineregisteredslot/)[]
***
### getTargeting()
> **getTargeting**(): [`RevTurbineTargeting`](/docs/api/interfaces/revturbinetargeting/)
Return the SDK-resolved targeting snapshot for the active user.
Includes user id, segment ids, traits, plan, and merged usage so demo
surfaces can display the same context used by placement eligibility.
#### Returns
[`RevTurbineTargeting`](/docs/api/interfaces/revturbinetargeting/)
***
### getTelemetryConsent()
> **getTelemetryConsent**(): [`TelemetryConsent`](/docs/api/type-aliases/telemetryconsent/)
The current behavior-telemetry consent state (plan 144 TASK-8).
#### Returns
[`TelemetryConsent`](/docs/api/type-aliases/telemetryconsent/)
***
### getTelemetryCounters()
> **getTelemetryCounters**(): `TelemetryCounters`
A snapshot of the telemetry pipeline counters (plan 144 TASK-8) —
created / dropped / redacted / sampled / deduped / queued / sent / failed.
A cheap diagnostic for "why did my events not arrive?". Returns a copy, so
mutating it never touches the SDK's live tallies.
#### Returns
`TelemetryCounters`
***
### getTrialStatus()
> **getTrialStatus**(): `Promise`\<\{ \}\>
Return the active user's trial status, derived from their trial
instances and the configured reverse-trial rules.
#### Returns
`Promise`\<\{ \}\>
***
### getUsage()
> **getUsage**(): [`RevTurbineUsageSnapshot`](/docs/api/type-aliases/revturbineusagesnapshot/)
Return the SDK-resolved usage snapshot for the active user.
Keys are usage units (derived from configured usage token prefixes when available),
and values include current usage plus optional limit when known.
#### Returns
[`RevTurbineUsageSnapshot`](/docs/api/type-aliases/revturbineusagesnapshot/)
***
### getUserContext()
> **getUserContext**(): `object`
Build the full persistence-ready UserContext from the current
SDK state. Includes `tenant_id` and `user_id` required for API storage.
Also projects the decision-driving fields — `trial` (the resolved trial
view), `tiers`, `payment_failed`, `payment_at_risk` and `instances` —
when held. The snapshot is a copy: changing it changes nothing in the SDK;
write through `update()` / `setUserContext()`.
#### Returns
`object`
***
### hydrate()
> **hydrate**(`payload`): `void`
Hydrate the SDK with a server-evaluated payload.
Call this on the client after receiving a `ServerEvaluationPayload`
from the server-side SDK. Pre-populates the decision cache,
entitlements, trial status, and user context so the client SDK
avoids redundant API calls.
#### Parameters
##### payload
#### Returns
`void`
#### Example
```ts
// In your React component / page hydration:
const session = await initRevTurbine({ tenantId, apiKey, endpoint, mode: 'react' });
session.sdk.hydrate(serverPayload);
```
***
### identify()
> **identify**\<`T`\>(`userId`, `context?`): `void`
Identify the current user and (optionally) supply their context.
Accepts the canonical [IdentifyContextInput](/docs/api/type-aliases/identifycontextinput/) — recognized by the
presence of `account_id` / `email` / `plan` / `plan_handle` / `usage` /
`entitlements` / `custom` — or a legacy plain-traits object, which is
stored under `custom` unchanged. `plan_handle` is THE plan matching
identity (the plan's `unique_handle`, plan 191 REQ-1); the `plan` object
is display metadata and never participates in matching, and nothing in
`custom` ever drives plan resolution (REQ-2).
Guardrails: an empty id rejects the call; an email-shaped id warns in dev
(ids stay opaque — pass emails as `{ email }`); unrecognized top-level
keys are named in a dev warning instead of being dropped or routed
silently. Legacy-traits behavior is unchanged beyond the diagnostic.
`account_id` is OPTIONAL. Identify no account and every event this SDK
emits carries `user-fallback:` instead — the acting user's id
behind the [FALLBACK\_ACCOUNT\_ID\_PREFIX](/docs/api/variables/fallback_account_id_prefix/) namespace marker, never a
bare user id (BL-0117, Kent's D-13). `events_clickstream.account_id` and
`placement_presentations.account_id` are the join keys
`monetization_funnel` matches accounts on, so the prefix is what lets the
warehouse keep a fabricated key out of an account denominator instead of
counting it as a real account. Identify an account when you have one.
#### Type Parameters
##### T
`T` *extends* [`IdentifyContextInput`](/docs/api/type-aliases/identifycontextinput/)
#### Parameters
##### userId
`string`
##### context?
`Exact`\<[`IdentifyContextInput`](/docs/api/type-aliases/identifycontextinput/), `T`\>
#### Returns
`void`
#### Example
```ts
rt.identify('user_123', { plan_handle: 'pro' }); // THE matching identity
rt.identify('user_123', { plan_handle: 'pro', plan: { handle: 'pro', name: 'Professional' } });
```
***
### onUserContextChange()
> **onUserContextChange**(`listener`): () => `void`
Subscribe to user-context changes — `identify()`, `setUserContext()`,
`update()`, `updateUsage()`, and `resetIdentity()` (plan 194 REQ-3).
Returns an unsubscribe function. Listeners must never throw; one that does
is caught here rather than allowed to break the verb that fired it.
#### Parameters
##### listener
() => `void`
#### Returns
() => `void`
#### Example
```ts
const unsubscribe = rt.onUserContextChange(() => refreshMyUi());
```
***
### persistPlacementTypes()
> **persistPlacementTypes**(`types`): `Promise`\<`void`\>
Persist authored placement types to a configured backend.
**Requires an explicit `placementTypes` endpoint override.** RevTurbine
hosts no `/api/sdk/placement-types` route — the default path was never
built, and there is no table behind it — so without an override this
method is a no-op rather than a guaranteed 404. It previously threw
`persist_placement_types_failed:404` in every non-`local_only` mode, which
made a documented method unusable on the documented default path.
Supply `endpointOverrides.placementTypes` (or override the provider hook of
the same name) to send these somewhere real.
#### Parameters
##### types
[`RevTurbinePlacementTypeEntity`](/docs/api/interfaces/revturbineplacementtypeentity/)[]
#### Returns
`Promise`\<`void`\>
***
### recordAccountCreated()
> **recordAccountCreated**(`payload`): `Promise`\<\{ `reason`: `string` \| `null`; `recorded`: `boolean`; \}\>
Record that a customer account came into existence (plan 276 REQ-3).
Requires the account grain, the creation time, the creating act and the
evidence that proves it. A **user** signup is not account creation
(plan 276 R-2) — emit `user_signed_up` for that; a payload whose `source`
names a user-grain act is rejected without emitting, and the result says so.
#### Parameters
##### payload
`AccountCreatedPayload`
#### Returns
`Promise`\<\{ `reason`: `string` \| `null`; `recorded`: `boolean`; \}\>
***
### recordClickThru()
> **recordClickThru**(`placementId`, `payloadId?`, `surfaceTemplateId?`, `metadata?`, `cooldownMs?`): `Promise`\<`void`\>
Record a bare placement click-through — the user clicked the CTA but did
not confirm the conversion (e.g. abandoned checkout). Time-boxed like a
dismiss; the placement may return (plan 167). For a confirmed conversion
use [recordConversion](/docs/api/classes/revturbinecustomersdk/#recordconversion).
#### Parameters
##### placementId
`string`
The placement's stable rule_id.
##### payloadId?
`string`
Optional payload variant id.
##### surfaceTemplateId?
`string`
Optional surface template id.
##### metadata?
[`RevTurbineImpressionMetadata`](/docs/api/type-aliases/revturbineimpressionmetadata/)
Optional metadata persisted with the record.
##### cooldownMs?
`number`
Optional explicit cooldown window (ms). Defaults to 7 days.
#### Returns
`Promise`\<`void`\>
***
### recordConversion()
> **recordConversion**(`placementId`, `payloadId?`, `surfaceTemplateId?`, `metadata?`): `Promise`\<`void`\>
Record a confirmed conversion — the user completed the CTA action. The
record is retained for analytics. Future eligibility is evaluated from
the current UserContext and Playbook.
#### Parameters
##### placementId
`string`
The placement's stable rule_id.
##### payloadId?
`string`
Optional payload variant id.
##### surfaceTemplateId?
`string`
Optional surface template id.
##### metadata?
[`RevTurbineImpressionMetadata`](/docs/api/type-aliases/revturbineimpressionmetadata/)
Optional metadata persisted with the record.
#### Returns
`Promise`\<`void`\>
***
### recordDismissal()
> **recordDismissal**(`placementId`, `payloadId?`, `surfaceTemplateId?`, `metadata?`, `cooldownMs?`): `Promise`\<`void`\>
Record a placement dismissal — the user explicitly closed it ("No thanks").
Time-boxed: the placement is hidden for the dismiss cooldown
(`cooldown_after_dismiss_days`, default 7 days) and re-shows once it elapses
(plan 167). NOT permanent — for a confirmed conversion use
[recordConversion](/docs/api/classes/revturbinecustomersdk/#recordconversion).
#### Parameters
##### placementId
`string`
The placement's stable rule_id.
##### payloadId?
`string`
Optional payload variant id.
##### surfaceTemplateId?
`string`
Optional surface template id.
##### metadata?
[`RevTurbineImpressionMetadata`](/docs/api/type-aliases/revturbineimpressionmetadata/)
Optional metadata persisted with the record.
##### cooldownMs?
`number`
Optional explicit cooldown window (ms). Defaults to 7 days.
#### Returns
`Promise`\<`void`\>
***
### recordImpression()
> **recordImpression**(`placementId`, `payloadId?`, `surfaceTemplateId?`, `metadata?`): `Promise`\<`void`\>
Record that a placement was rendered to the user. Plan 43 TASK-9.
Writes an `impressed` record to the SDK's `ImpressionHistory`,
which persists to `localStorage` via `StorageImpressionStore`
(or in-memory storage in non-browser environments). The
impression contributes to:
- **Frequency caps (`cap.v1`)** — once-per-period limits
count this impression against the user's quota for the
placement.
- **Trial milestone supersession analytics** — for trial_progress
ladders, the supersession diagnostic uses delivery state
to distinguish "replaced an undelivered placement" (counts
in `superseded_placement_ids`) from "lower threshold was
already shown" (NOT counted — spec §3.5 "supersession only
applies to undelivered placements").
- **Generic milestone supersession (`content.milestone_order`)**
— the order-based variant in `applyContentMilestoneSupersession`.
Call this from consumer code when the placement is actually
shown to the user (e.g., from a React component's `useEffect`
on mount, or after the rendering call returns). The SDK does
NOT auto-record impressions — surfaces are rendered by consumer
code, so only the consumer knows when a placement was actually
presented (vs. fetched but not displayed).
Safe to call multiple times for the same placement — duplicates
append additional impression records (used by frequency caps to
count delivery events).
#### Parameters
##### placementId
`string`
The placement's stable rule_id (e.g.
`'pl_trial_progress_70'`). Match `decision.output.rule_id`
from `getPlacementDecision`.
##### payloadId?
`string`
Optional payload variant id, for
variant-level analytics.
##### surfaceTemplateId?
`string`
Optional surface template id, for
per-surface cap accounting.
##### metadata?
[`RevTurbineImpressionMetadata`](/docs/api/type-aliases/revturbineimpressionmetadata/)
Optional metadata persisted with the record.
#### Returns
`Promise`\<`void`\>
#### Example
```ts
const decision = await sdk.getPlacementDecision({ placementId: 'slot_trial_modal' });
if (decision.visible && decision.output?.rule_id) {
renderBanner(decision.output);
await sdk.recordImpression(decision.output.rule_id, decision.output.output_id);
}
```
***
### recordTrialRevision()
> **recordTrialRevision**(`episode`, `labels?`): `Promise`\<`TrialRevisionRecordResult`\>
Record one app-owned trial-episode revision (plan 276 R-1(b), TASK-13).
**Trial execution and ownership stay with your app.** RevTurbine runs no
trial, enrols nobody and decides nothing about when a trial ended; it
records the fact your app states. Pass the episode's facts — including the
`evidence` that proves the revision — and the SDK runs the same pure
classifier every RevTurbine port runs, then emits one `trial_revision` on
the typed platform lane if, and only if, the evidence supports one.
The classifier reads **no clock**. An episode whose scheduled end has
merely passed emits nothing and comes back `pending_unknown`: an elapsed
deadline proves only that the time passed, never that your app ended
anything. Supply `actual_end_at` and an `end_evidence` when it did.
In `local_only` mode the SDK makes no server calls, so the fact reaches any
registered event consumer and never RevTurbine; the result says
`recorded_locally` rather than pretending otherwise.
Never throws, and never blocks a decision.
#### Parameters
##### episode
`TrialEpisodeFacts`
The episode's facts, as your app holds them.
##### labels?
`TrialRevisionLabels` = `{}`
Playbook vocabulary that rides along for grouping:
`rule_handle`, `plan_handle`, `trial_type`, `provider_ref`. None of it
affects the classification (plan 276 R-1(c)).
#### Returns
`Promise`\<`TrialRevisionRecordResult`\>
***
### ~~registerPlacement()~~
> **registerPlacement**(`config`): `Promise`\<`string`\>
:::caution[Deprecated]
Use `registerSurfaceSlot`.
:::
#### Parameters
##### config
[`RevTurbinePlacementConfig`](/docs/api/interfaces/revturbineplacementconfig/)
#### Returns
`Promise`\<`string`\>
***
### registerSurfaceSlot()
> **registerSurfaceSlot**(`config`): `Promise`\<`string`\>
Register a surface slot with the local runtime and return its placement id.
**Client-local by default** (BL-0197): this performs no network write. The
slot is recorded in the in-process registry, and the pipeline discovers it
from the `slot_*` / `placement_*` telemetry the SDK emits through
`/api/track`. A write happens only when `custom_endpoints.surfaceSlots`
points at a slot-inventory service you run; there is no fallback to
`/api/placements`.
#### Parameters
##### config
[`RevTurbineSurfaceSlotConfig`](/docs/api/interfaces/revturbinesurfaceslotconfig/)
#### Returns
`Promise`\<`string`\>
***
### reportSdkError()
> **reportSdkError**(`reason`, `message?`): `void`
Report that the SDK itself failed (plan 182 TASK-5).
`sdk_error` was declared in the meta taxonomy with no emitter. This is the
"the SDK malfunctioned" signal — distinct from `resolution_failure`, which
means a *decision* produced nothing. It rides the same anonymous
`events_sdk_meta` lane: no tenant, no user, no placement identity.
Deduped on `reason` for the session and capped, mirroring
emitResolutionFailure — an SDK failing inside a render loop must
not become a telemetry flood. Best-effort and never throwing: this is
called from `catch` blocks, so it must not manufacture a second failure.
#### Parameters
##### reason
`string`
Stable, categorical cause (e.g. `provider_init_failed`) —
what dashboards group by.
##### message?
`string`
Human-readable detail for triage, sent on the same footing
as `sdk_validation_warning`'s message, which already ships on this lane.
#### Returns
`void`
***
### reset()
> **reset**(): `void`
Clear the current user — the advertised alias of [resetIdentity](/docs/api/classes/revturbinecustomersdk/#resetidentity)
(e.g. on sign-out).
#### Returns
`void`
***
### resetIdentity()
> **resetIdentity**(): `void`
Reset to anonymous user state, re-inferring a fresh anonymous id. Also
available as the advertised [reset](/docs/api/classes/revturbinecustomersdk/#reset) alias.
#### Returns
`void`
***
### resetUserContext()
> **resetUserContext**(): `void`
Hard-reset the user context to a blank slate — removes EVERY user-context
value (`id`, `plan`, `email`, `account_id`, `custom`, `usage`,
`entitlements`, `personalization`) plus usage balances, and clears the
decision cache, interaction state, and impression history.
Unlike [resetIdentity](/docs/api/classes/revturbinecustomersdk/#resetidentity) (a sign-out that re-infers anonymous context
when the `inferUser` policy is on), this performs **no** inference, so the
resulting context is guaranteed empty. Mostly for demo / fixture flows that
reset cleanly between scenarios.
#### Returns
`void`
#### Example
```ts
// Between demo personas:
rt.resetUserContext();
rt.identify('demo_pro', { plan_handle: 'pro' });
```
***
### setApiBranding()
> **setApiBranding**(`branding`): `void`
Supply the branding-API rung after init.
`apiBranding` can be passed at construction when the host fetches branding
itself, but with `fetchThemeOverride` the SDK fetches
`GET /api/sdk/theme` *after* the instance exists. Without this setter that
fetched value reached only the React theme context, so `getBranding()` and
`useRevTurbineTheme()` could report different branding for the same tenant
(plan 184).
#### Parameters
##### branding
\{ \} \| `undefined`
Branding from the Branding API. Overrides config-embedded
branding but never the explicit `branding` init option.
#### Returns
`void`
***
### setTelemetryConsent()
> **setTelemetryConsent**(`consent`): `void`
Update behavior-telemetry consent at runtime (plan 144 TASK-8 / REQ-12).
Takes effect on the next event with **no** provider remount: `denied` /
`pending` stop event creation immediately, so nothing reaches ingest,
registered consumers, or integrations; `granted` resumes emission. Does not
affect the keyless anonymous SDK-init beacon, which has its own
RevTurbineInitOptions.anonymousTelemetry switch (REQ-13).
#### Parameters
##### consent
[`TelemetryConsent`](/docs/api/type-aliases/telemetryconsent/)
the new consent state
#### Returns
`void`
***
### setTrialInstances()
> **setTrialInstances**(`instances`, `options?`): `Promise`\<\{ \}\>
Derive and push the user's trial status from their TrialInstance
records, resolving the matching free/reverse trial rule straight from the
initialized RevTurbineConfig's `free_trial_rules` / `reverse_trial_rules`.
The config-driven counterpart to setTrialStatus: instead of the
host app pre-deriving a `UserTrialStatus`, the SDK evaluates the tenant's
trial rules against the supplied instances via `@revt-eng/core`'s
`evaluateTrialStatus` and pushes the result into the local trial context
that gates `trial_progress` / `trial_ending` / `trial_ended` /
`trial_converted` placements. The same evaluator runs in the Python server
SDK, so both decide identically.
Requires an initialized Playbook (static mode, or any mode with a
config provider). Returns the derived status, or `{ in_trial: false }`
when no active trial instance applies.
#### Parameters
##### instances
readonly `object`[]
The user's trial instance records.
##### options?
Optional `nowIso` clock pin (defaults to the current
time) and `basePlanHandle` (the base plan a reverse-trial user reverts
to, surfaced as `plan_handle`).
###### basePlanHandle?
`string`
###### nowIso?
`string`
#### Returns
`Promise`\<\{ \}\>
***
### setUserContext()
> **setUserContext**(`userContext`): `void`
Merge fields into the session-bound user context. Recognized fields
upsert; an omitted field never clobbers a previously-set value. See the
advertised [update](/docs/api/classes/revturbinecustomersdk/#update) alias for the full recognized-field contract.
Server-assigned fields (`seat_type_handle`, `activity_score`,
`activity_score_computed_at`) are dropped and reported, never merged:
they are written only by the tenant's backend or RevTurbine's
enrichment (D-36, BL-0352). The parameter type does not yet reject other
undeclared keys the way [identify](/docs/api/classes/revturbinecustomersdk/#identify) and [update](/docs/api/classes/revturbinecustomersdk/#update) do; that
`Exact<>` narrowing is deferred to `0.12.0` because it can break callers
that pass a wider object today.
#### Parameters
##### userContext
[`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)
#### Returns
`void`
***
### snooze()
> **snooze**(`outputId`, `seconds?`): `Promise`\<`void`\>
Suppress a decision's placement for a "remind me later" window —
`seconds` overrides the authored default.
#### Parameters
##### outputId
`string`
##### seconds?
`number`
#### Returns
`Promise`\<`void`\>
***
### syncTrialRevisions()
> **syncTrialRevisions**(`labels?`): `Promise`\<`TrialRevisionRecordResult`[]\>
Record every revision the registered trial provider currently states.
Resolves the `'trial'` domain provider, classifies each episode it returns
and records the ones whose evidence supports a revision. An episode that
resolves to `pending_unknown` is reported and emits nothing.
With **no trial provider registered** this records nothing, logs the
one-time notice naming the gap, and returns a single
`no_trial_provider` result — never an error (ruling D-21). Call
[RevTurbineCustomerSdk.recordTrialRevision](/docs/api/classes/revturbinecustomersdk/#recordtrialrevision) directly if you would
rather push episodes than have them pulled.
#### Parameters
##### labels?
`TrialRevisionLabels` = `{}`
#### Returns
`Promise`\<`TrialRevisionRecordResult`[]\>
***
### track()
> **track**(`name`, `data?`): `Promise`\<`void`\>
Track an event — the advertised alias of trackEvent. Powers
analytics, frequency caps, attribution, and experiments.
Identity comes from the active user context ([identify](/docs/api/classes/revturbinecustomersdk/#identify) /
[setUserContext](/docs/api/classes/revturbinecustomersdk/#setusercontext)). `account_id` carries the identified account when
there is one and the labelled fallback `user-fallback:` when there
is not — never a bare, unlabelled user id (BL-0117, Kent's D-13).
#### Parameters
##### name
`string`
##### data?
[`SdkEventProperties`](/docs/api/type-aliases/sdkeventproperties/)
#### Returns
`Promise`\<`void`\>
#### Example
```ts
rt.track('ai_generation_completed', { credits: 3 });
@public
```
***
### trackControlPlaneEvent()
> **trackControlPlaneEvent**(`eventType`, `payload?`, `options?`): `Promise`\<`void`\>
Emit a typed control-plane semantic event (plan 112).
The dogfood-faithful surface for RevTurbine's own product activity:
`eventType` is constrained to the canonical ControlPlaneEventType
taxonomy and the `system`/`workflow` source classification is stamped
automatically. Forwards through the same ingest + consumer path as
capture — so the event lands in clickstream AND any registered
analytics resolver (e.g. a PostHog provider from
[createPostHogAnalyticsProvider](/docs/api/functions/createposthoganalyticsprovider/)).
Identity comes from the active user context set via [identify](/docs/api/classes/revturbinecustomersdk/#identify) /
[setUserContext](/docs/api/classes/revturbinecustomersdk/#setusercontext): the operator is `user_id` and the acting RevTurbine
customer tenant is `account_id`. `tenant_id` is stamped server-side and is
never carried on the event (plan 112 REQ-3/REQ-4). When no account has been
identified, `account_id` carries the labelled fallback
`user-fallback:` — a bare, unlabelled user id is never stamped
into an account column (BL-0117, Kent's D-13). Classify it with
[isFallbackAccountId](/docs/api/functions/isfallbackaccountid/).
#### Parameters
##### eventType
`"web_signed_up"` \| `"web_signed_in"` \| `"cli_signed_up"` \| `"cli_signed_in"` \| `"cli_command_executed"` \| `"playbook_version_submitted"` \| `"playbook_version_approved"` \| `"playbook_version_rejected"` \| `"playbook_version_deployed"` \| `"playbook_version_launched"` \| `"playbook_version_parked"` \| `"playbook_version_resumed"` \| `"playbook_version_discarded"` \| `"playbook_version_archived"` \| `"playbook_imported"` \| `"playbook_exported"` \| `"entity_created"` \| `"entity_updated"` \| `"entity_deleted"` \| `"web_api_error"`
A canonical control-plane event type.
##### payload?
[`SdkEventProperties`](/docs/api/type-aliases/sdkeventproperties/) = `{}`
Optional event-specific properties (e.g. `{ resource, resource_id }`).
##### options?
`RevTurbineEventOptions`
Emit options, e.g. `{ immediate: true }` to bypass batching.
#### Returns
`Promise`\<`void`\>
#### Example
```ts
sdk.identify('operator_42', { account_id: 'tn_acme' });
await sdk.trackControlPlaneEvent('playbook_version_deployed', { change_set_id: 'cs_9' });
```
***
### trackTreatmentInteraction()
> **trackTreatmentInteraction**(`input`): `Promise`\<`void`\>
Report a treatment interaction directly by placement/treatment id — the
lower-level counterpart to [dismiss](/docs/api/classes/revturbinecustomersdk/#dismiss)/[snooze](/docs/api/classes/revturbinecustomersdk/#snooze)/[convert](/docs/api/classes/revturbinecustomersdk/#convert),
which key off a decision's `output_id` instead.
`decisionId`, `segmentHandles` and `segmentIds` (plan 282 TASK-9) ride
the interaction onto the wire as `decision_id` / `segment_handles` /
`segment_ids`. All optional: given, they win; otherwise the SDK fills
them from the output it indexed for `payloadId` (`segment_ids` only when
a hosted context supplied minted ids — never in local mode); a caller
that supplies none and names no indexed output sends exactly what it
sent before.
A `cta_clicked` on an indexed output whose CTA opens a plan picker or
pricing page ([CHECKOUT\_STARTED\_CTA\_ACTION\_TYPES](/docs/api/variables/checkout_started_cta_action_types/)) also emits
`checkout_started` — never for a CTA that opens checkout directly.
#### Parameters
##### input
`RevTurbineTreatmentInteractionInput`
#### Returns
`Promise`\<`void`\>
***
### update()
> **update**\<`T`\>(`patch`): `void`
Patch the session-bound user context — the advertised `update(patch)` verb.
Accepts every session-scoped [RevTurbineUserContext](/docs/api/interfaces/revturbineusercontext/) field except the
identity handle `id` (see [RevTurbineUpdateInput](/docs/api/type-aliases/revturbineupdateinput/)); to (re)establish
identity use [identify](/docs/api/classes/revturbinecustomersdk/#identify).
`usage` routes through [updateUsage](/docs/api/classes/revturbinecustomersdk/#updateusage) (absolute balances + usage
threshold-crossing events); every other recognized field routes through
[setUserContext](/docs/api/classes/revturbinecustomersdk/#setusercontext), which merges with upsert semantics — an omitted field
never clobbers a previously-set value. Both stores are updated independently,
so `update({ plan, usage })` applies each without interference. Keys outside
the recognized set are dropped with a dev-only warning — they never reach
the context, and `update({ : n })` is not a usage report.
#### Type Parameters
##### T
`T` *extends* [`RevTurbineUpdateInput`](/docs/api/type-aliases/revturbineupdateinput/)
#### Parameters
##### patch
`Exact`\<[`RevTurbineUpdateInput`](/docs/api/type-aliases/revturbineupdateinput/), `T`\>
#### Returns
`void`
#### Examples
```ts
// Bump reported usage:
rt.update({ usage: { generations: 25 } });
```
```ts
// Reflect a plan change and new traits in one call (identity unchanged):
rt.update({ plan_handle: 'pro', custom: { role: 'admin' } });
@public
```
***
### updateUsage()
> **updateUsage**(`balances`): `void`
Set absolute usage balances for the active user and emit any
threshold-crossing triggers. See the advertised [update](/docs/api/classes/revturbinecustomersdk/#update) alias's
`usage` key for the same contract via the general patch verb.
#### Parameters
##### balances
[`UsageBalances`](/docs/api/type-aliases/usagebalances/)
#### Returns
`void`
***
### validateUiPathResolvers()
> **validateUiPathResolvers**(`options?`): `Promise`\<`RevTurbineUiPathResolverValidationReport`\>
Validate that each configured UI path action has a resolver implementation.
By default this validates `localRuntime.playbook.content_ui_paths` (when present)
against:
- `uiPathResolvers` passed at SDK init
- optional `resolvers` passed to this method
- CTA handlers from domain providers (`domain: 'cta'`), unless disabled
#### Parameters
##### options?
`RevTurbineUiPathResolverValidationOptions` = `{}`
#### Returns
`Promise`\<`RevTurbineUiPathResolverValidationReport`\>
# RevTurbineServer
The RevTurbine server-side client. Holds the server key; mints client
sessions ([RevTurbineServer.createClientSession](/docs/api/classes/revturbineserver/#createclientsession)), writes
server-only user state ([RevTurbineServer.assignSeatType](/docs/api/classes/revturbineserver/#assignseattype)) and reads
the user's server-resolved built-in dimensions
([RevTurbineServer.getBuiltinDimensions](/docs/api/classes/revturbineserver/#getbuiltindimensions)).
## Accessors
### clientSessions
#### Get Signature
> **get** **clientSessions**(): `object`
Client-session minting namespace (plan 157). Ergonomic form of
[createClientSession](/docs/api/classes/revturbineserver/#createclientsession):
##### Example
```ts
const { client_token, expires_at } = await server.clientSessions.create({
subject: session.user.id,
});
// return client_token to the frontend
```
##### Returns
`object`
###### create
> **create**: (`input`) => `Promise`\<`ClientSessionResult`\>
###### Parameters
###### input
`CreateClientSessionInput`
###### Returns
`Promise`\<`ClientSessionResult`\>
## Methods
### assignSeatType()
> **assignSeatType**(`userId`, `seatTypeHandle`, `options?`): `Promise`\<\{ \}\>
Assign a user's seat type, or clear it with `null` (plan 279 TASK-16a).
Calls `POST /api/sdk/user-contexts` (`upsertServerUserContext`) with exactly
`{ user_id, seat_type_handle }`, authenticated by the server key this client
holds. The tenant is taken from that key — it is never a parameter, and no
tenant header is sent.
**Server-written only (D-36).** A user's seat-type assignment is written
only by the tenant's backend through this server-key call (or by RevTurbine
enrichment). It is never set from the browser: the client SDK's
`identify()` / `update()` do not accept `seat_type_handle`, and the endpoint
refuses every credential except a server key.
Pass `options.builtinDimensions` (and `options.override`) to set built-in
dimension values in the same write — see [setBuiltinDimensions](/docs/api/classes/revturbineserver/#setbuiltindimensions) for
their semantics (BL-0382).
#### Parameters
##### userId
`string`
The tenant's own identifier for the user (non-empty).
##### seatTypeHandle
`string` \| `null`
One of the tenant's current seat-type handles, or
`null` to clear the user's assignment.
##### options?
`AssignSeatTypeOptions` = `{}`
Optional built-in dimension values to write with the seat.
#### Returns
`Promise`\<\{ \}\>
The resulting state, including the stored `seat_type_handle`, the
customer-set `builtin_dimensions`, the pinned `overrides` and `updated_at`.
#### Throws
if the control plane refuses the
write — `reason` is `unknown_seat_type` for a 422 on the handle,
`unknown_dimension_value` for a 422 on a dimension value, and `forbidden`
for a 403 (the key is not a server key). The error never carries the key.
#### Example
```ts
await server.assignSeatType('user_123', 'admin');
await server.assignSeatType('user_123', null); // clear the assignment
```
***
### createClientSession()
> **createClientSession**(`input`): `Promise`\<`ClientSessionResult`\>
Mint a short-lived, opaque per-user client-session token (plan 157; the
server key is the minting authority, plan 256).
The customer backend — which holds the server key (passed as
[RevTurbineServerOptions.apiKey](/docs/api/interfaces/revturbineserveroptions/#apikey)) — calls this to obtain a browser-safe
`rt_client_` token scoped to one end-user subject, then returns the token to
its frontend. The frontend authenticates `GET /api/sdk/client-context` with
it to read the user's client-safe context.
This is a **server-only** capability: the browser SDK never mints tokens (it
only consumes them). The tenant is derived server-side from the server key,
never from this call.
#### Parameters
##### input
`CreateClientSessionInput`
#### Returns
`Promise`\<`ClientSessionResult`\>
#### Throws
if the control plane rejects the mint.
The error carries only the HTTP status + request id — never the key.
***
### getBuiltinDimensions()
> **getBuiltinDimensions**(`userId`): `Promise`\<\{ \}\>
Read the control plane's server-resolved built-in segment dimensions for
one user (BL-0366).
Calls `GET /api/sdk/user-contexts/{userId}/builtin-dimensions`
(`getServerUserBuiltinDimensions`), authenticated by the server key this
client holds. The tenant is taken from that key — it is never a parameter,
and no tenant header is sent. The result's `builtin_dimensions` carries the
values the browser SDK would receive through the client context
(Subscription State, Trial Type, Activity State, Seat Type, Buyer Role,
Billing Health, Email Type); `region` / `device_type` are request-derived
and never present. An unknown user resolves to each dimension's default.
Feed it to local evaluation as the server overlay: pass
`result.builtin_dimensions` as `createStaticProviders({ …, userContext,
serverBuiltinDimensions })`, which overlays it per key on
`userContext.builtin_dimensions` — the server value wins (plan 279 PD-3).
The Python and Rust server SDKs accept the same object through
`server_builtin_dimensions`.
#### Parameters
##### userId
`string`
The tenant's own identifier for the user (non-empty) — the
same key `assignSeatType` and `createClientSession` use.
#### Returns
`Promise`\<\{ \}\>
`{ tenant_id, user_id, builtin_dimensions, resolved_at }`.
#### Throws
if the control plane refuses the
read — `reason` is `forbidden` for a 403 (the key is not a server key).
The error never carries the key.
#### Example
```ts
const { builtin_dimensions } = await server.getBuiltinDimensions('user_123');
const providers = createStaticProviders({
config: playbook,
userContext: { id: 'user_123' },
serverBuiltinDimensions: builtin_dimensions,
});
```
***
### setBuiltinDimensions()
> **setBuiltinDimensions**(`userId`, `dimensions`, `options?`): `Promise`\<\{ \}\>
Set a user's built-in segment dimension values from your backend (BL-0382,
rulings D-46 / D-47).
Calls `POST /api/sdk/user-contexts` (`upsertServerUserContext`) with
`{ user_id, builtin_dimensions, override }`, authenticated by the server key
this client holds. The tenant is taken from that key — never a parameter.
The write is an UPSERT per dimension: dimensions you do not name are left
alone, and `null` clears a dimension (its value and its pin). The seat
assignment is untouched (use [assignSeatType](/docs/api/classes/revturbineserver/#assignseattype)).
Settable dimensions: `subscription_state`, `trial_type`, `billing_health`,
`activity_level`, `buyer_role`, `email_type`, `region`, `device_type` —
each takes a value from its closed vocabulary (the types enforce it).
`seat_type` is not settable here; it is `assignSeatType`.
The control plane applies the value before delivery, so both the browser
SDK's client context and [getBuiltinDimensions](/docs/api/classes/revturbineserver/#getbuiltindimensions) return it:
- `override: false` (default): delivered until a newer RevTurbine
enrichment change supersedes it (last writer wins, enrichment included);
writing a pinned dimension without the flag unpins it.
- `override: true`: pinned — enrichment never replaces it until you write
the dimension again without the flag, or clear it.
#### Parameters
##### userId
`string`
The tenant's own identifier for the user (non-empty) — the
same key `assignSeatType` and `createClientSession` use.
##### dimensions
Dimension → value (or `null` to clear). Must name at
least one dimension.
##### options?
`SetBuiltinDimensionsOptions` = `{}`
`{ override }` — see above.
#### Returns
`Promise`\<\{ \}\>
The resulting state: `seat_type_handle`, every customer-set
`builtin_dimensions` value now stored, the pinned `overrides`, and
`updated_at`.
#### Throws
if the control plane refuses the
write — `reason` is `unknown_dimension_value` for a 422 and `forbidden`
for a 403 (the key is not a server key). The error never carries the key.
#### Example
```ts
// Your billing system knows better than RevTurbine's Stripe view: pin it.
await server.setBuiltinDimensions('user_123', { subscription_state: 'paid' }, { override: true });
// Hand the dimension back to RevTurbine enrichment.
await server.setBuiltinDimensions('user_123', { subscription_state: null });
```
# SdkSession
A fully-initialized SDK session with convenience methods for creating
placement and entitlement controllers, updating user context,
and accessing the underlying SDK instance.
This is the recommended entry point for headless (non-React) consumers.
## Example
```ts
const session = await initRevTurbine({
tenantId: 'tenant_abc',
publicKey: 'rtk_…',
endpoint: 'https://edge.example.com',
mode: 'snippet',
user: { id: 'user_123', plan_handle: 'pro' },
});
// Get a placement by slot ID
const banner = session.placement({ surfaceSlot: { id: 'upsell_banner' } });
const decision = await banner.load();
// Check an entitlement with auto-gate
const gate = session.entitlement({ handle: 'brand_kit', autoGate: true });
await gate.check();
// Update user context (works in any runtime mode)
session.identify('user_456', { plan_handle: 'enterprise' });
session.setUserContext({ personalization: { company: 'Acme' } });
```
## Properties
### sdk
> `readonly` **sdk**: [`RevTurbineCustomerSdk`](/docs/api/classes/revturbinecustomersdk/)
The underlying SDK instance. Use for advanced/direct operations.
***
### theme
> `readonly` **theme**: [`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)
Resolved theme.
## Methods
### can()
> **can**(`handle`, `context?`): `Promise`\<\{ \}\>
The entitlement check as a `can` question — alias of
[checkEntitlement](/docs/api/classes/sdksession/#checkentitlement), promoted onto the session facade so
`session.can('batch_export')` works without the `session.sdk` escape
hatch (plan 179 Q-1/Q-3 ruling). Read `.allowed` for the verdict; an
at-cap blocked limit resolves `limited` + `allowed: false`.
#### Parameters
##### handle
`string`
##### context?
`RevTurbineEntitlementContext`
#### Returns
`Promise`\<\{ \}\>
***
### checkEntitlement()
> **checkEntitlement**(`handle`, `context?`): `Promise`\<\{ \}\>
One-shot entitlement check. For auto-gating or reactive updates,
prefer [entitlement](/docs/api/classes/sdksession/#entitlement) which returns a full controller.
#### Parameters
##### handle
`string`
##### context?
`RevTurbineEntitlementContext`
#### Returns
`Promise`\<\{ \}\>
***
### entitlement()
> **entitlement**(`options`): `EntitlementGate`
Create an EntitlementGate bound to this session's SDK.
#### Parameters
##### options
[`EntitlementGateOptions`](/docs/api/interfaces/entitlementgateoptions/)
#### Returns
`EntitlementGate`
#### Example
```ts
const gate = session.entitlement({ handle: 'brand_kit', autoGate: true });
await gate.check();
if (gate.denied) { showGate(gate.gatedPlacement); }
```
***
### fetchUserContext()
> **fetchUserContext**(`userId`): `Promise`\<`UserTargetingContext`\>
Fetch full user context from the server (server runtime mode).
#### Parameters
##### userId
`string`
#### Returns
`Promise`\<`UserTargetingContext`\>
***
### flushEvents()
> **flushEvents**(): `Promise`\<`void`\>
Flush buffered events immediately. Headless processes are often
short-lived (scripts, jobs, edge handlers) — call this before exit so
buffered `track()` events aren't lost with the process.
#### Returns
`Promise`\<`void`\>
***
### getPlacement()
> **getPlacement**(`config`): `Promise`\<\{ \} \| `null`\>
Get a raw placement output by request config (slot, entitlement, plan, or chained).
Returns the full PlacementOutput or `null`.
#### Parameters
##### config
`RevTurbinePlacementRequestConfig`
#### Returns
`Promise`\<\{ \} \| `null`\>
***
### getPlacementBySlotId()
> **getPlacementBySlotId**(`slotId`, `options?`): `Promise`\<`RevTurbinePlacementDecision` \| `null`\>
One-shot: register a surface slot, fetch a decision, and return it.
For repeated use or interaction tracking, prefer [placement](/docs/api/classes/sdksession/#placement) which
returns a full controller.
#### Parameters
##### slotId
`string`
##### options?
`Omit`\<[`PlacementControllerOptions`](/docs/api/interfaces/placementcontrolleroptions/), `"placement"` \| `"surfaceSlot"`\>
#### Returns
`Promise`\<`RevTurbinePlacementDecision` \| `null`\>
***
### getTrialStatus()
> **getTrialStatus**(): `Promise`\<\{ \}\>
Get the current trial status.
#### Returns
`Promise`\<\{ \}\>
***
### getUsage()
> **getUsage**(): [`RevTurbineUsageSnapshot`](/docs/api/type-aliases/revturbineusagesnapshot/)
Get a snapshot of current usage balances.
#### Returns
[`RevTurbineUsageSnapshot`](/docs/api/type-aliases/revturbineusagesnapshot/)
***
### getUserContext()
> **getUserContext**(): `object`
Get the current resolved user context (includes `tenant_id`, `user_id`).
#### Returns
`object`
***
### identify()
> **identify**(`userId`, `contextOrTraits?`): `void`
Identify a user and optionally set traits/context.
Triggers segment re-evaluation and clears decision cache.
#### Parameters
##### userId
`string`
##### contextOrTraits?
`Exact`\<[`IdentifyContextInput`](/docs/api/type-aliases/identifycontextinput/), [`IdentifyContextInput`](/docs/api/type-aliases/identifycontextinput/)\>
#### Returns
`void`
***
### placement()
> **placement**(`options`): [`PlacementController`](/docs/api/classes/placementcontroller/)
Create a [PlacementController](/docs/api/classes/placementcontroller/) bound to this session's SDK.
#### Parameters
##### options
[`PlacementControllerOptions`](/docs/api/interfaces/placementcontrolleroptions/)
#### Returns
[`PlacementController`](/docs/api/classes/placementcontroller/)
#### Example
```ts
const banner = session.placement({ surfaceSlot: { id: 'upsell_banner' } });
await banner.load();
if (banner.visible) { renderBanner(banner.content); }
```
***
### resetIdentity()
> **resetIdentity**(): `void`
Reset to anonymous user state.
#### Returns
`void`
***
### resetUserContext()
> **resetUserContext**(): `void`
Hard-reset the user context to a blank slate (no anonymous inference) —
removes every user-context value plus usage balances and clears the
decision cache, interaction state, and impression history. Mostly for
demo / fixture flows. See [RevTurbineCustomerSdk.resetUserContext](/docs/api/classes/revturbinecustomersdk/#resetusercontext).
#### Returns
`void`
***
### setUserContext()
> **setUserContext**(`context`): `void`
Merge fields into the current user context.
Triggers segment re-evaluation.
#### Parameters
##### context
[`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)
#### Returns
`void`
***
### track()
> **track**(`name`, `data?`): `Promise`\<`void`\>
Track an event — the advertised alias of [trackEvent](/docs/api/classes/sdksession/#trackevent), first-class
on the session facade so headless code carries the full telemetry surface
(plan 179 ruling, Kent 2026-08-13). Powers analytics, frequency caps,
attribution, and experiments.
#### Parameters
##### name
`string`
##### data?
`Record`\<`string`, `JsonValue`\>
#### Returns
`Promise`\<`void`\>
***
### trackEvent()
> **trackEvent**(`name`, `data?`): `Promise`\<`void`\>
Track a custom event.
#### Parameters
##### name
`string`
##### data?
`Record`\<`string`, `JsonValue`\>
#### Returns
`Promise`\<`void`\>
***
### update()
> **update**(`patch`): `void`
Patch the session-scoped user context — the whole-context-minus-`id`
upsert (`update({ plan: {...} })`, `update({ usage: {...} })`, …).
Alias of the SDK's `update()`, promoted onto the session facade so the
documented `session.update()` verb is real (plan 179 Q-1/Q-3 ruling).
Unrecognized keys warn (prod-visible, once per session) and drop, exactly
as on the SDK — plan 191 Q-5 made the warning prod-visible rather than
dev-only, so this comment was stale in the direction that matters.
#### Parameters
##### patch
[`RevTurbineUpdateInput`](/docs/api/type-aliases/revturbineupdateinput/)
#### Returns
`void`
***
### updateUsage()
> **updateUsage**(`balances`): `void`
Update usage balances (e.g. after a meter event).
#### Parameters
##### balances
[`UsageBalances`](/docs/api/type-aliases/usagebalances/)
#### Returns
`void`
# ServerUserContextProvider
A TraitsProvider keyed to `traits:server`. Auto-wired by the SDK when a
client-context token is configured; call [setSnapshot](/docs/api/classes/serverusercontextprovider/#setsnapshot) whenever a fresh
server context is fetched.
## Implements
- `TraitsProvider`\<`"traits:server"`\>
## Methods
### hasSignals()
> **hasSignals**(): `boolean`
Whether any server signal is currently present.
#### Returns
`boolean`
***
### resolve()
> **resolve**(): `TraitsProviderState`
Resolve the current `traits:server` state for evaluation.
#### Returns
`TraitsProviderState`
#### Implementation of
`TraitsProvider.resolve`
***
### setSnapshot()
> **setSnapshot**(`snapshot`): `void`
Replace the server-known snapshot (called after each client-context fetch).
#### Parameters
##### snapshot
[`ServerUserContextSnapshot`](/docs/api/interfaces/serverusercontextsnapshot/)
#### Returns
`void`
# UnsupportedExperimentAssignmentUnitError
Raised before provider registration when its subject cannot serve an experiment's unit.
## Extends
- `Error`
## Constructors
### Constructor
> **new UnsupportedExperimentAssignmentUnitError**(`experimentHandle`, `requestedUnit`, `subjectUnit`): `UnsupportedExperimentAssignmentUnitError`
Construct an unsupported-unit launch error.
#### Parameters
##### experimentHandle
`string`
##### requestedUnit
[`NativeExperimentAssignmentUnit`](/docs/api/type-aliases/nativeexperimentassignmentunit/)
##### subjectUnit
[`NativeExperimentAssignmentUnit`](/docs/api/type-aliases/nativeexperimentassignmentunit/)
#### Returns
`UnsupportedExperimentAssignmentUnitError`
#### Overrides
`Error.constructor`
## Properties
### experimentHandle
> `readonly` **experimentHandle**: `string`
Experiment whose requested assignment unit cannot be served.
***
### requestedUnit
> `readonly` **requestedUnit**: [`NativeExperimentAssignmentUnit`](/docs/api/type-aliases/nativeexperimentassignmentunit/)
Assignment unit requested by the canonical Experiment version.
***
### subjectUnit
> `readonly` **subjectUnit**: [`NativeExperimentAssignmentUnit`](/docs/api/type-aliases/nativeexperimentassignmentunit/)
Unit represented by the provider's configured subject.
# AccessDeniedPlaceholder
> **AccessDeniedPlaceholder**(`__namedParameters`): `any`
Default denied state for an AccessGateSurfaceSlot (D-59,
2026-10-06): rendered when access is denied and no Access Gate placement
is authored for the slot's entitlement, so a blocked user always sees that
the feature is unavailable — never another entitlement's gate, never a
blank. An app-supplied `deniedFallback` replaces it.
## Parameters
### \_\_namedParameters
`AccessDeniedPlaceholderProps`
## Returns
`any`
# adaptExperimentVersionToBucketer
> **adaptExperimentVersionToBucketer**(`experiment`): [`BasicBucketerExperiment`](/docs/api/interfaces/basicbucketerexperiment/)
Translate canonical fraction weights into the bucketer's ratio + exposure
representation.
For weights whose positive sum is below one, the missing fraction remains
unenrolled: `[0.3, 0.3]` becomes ratio `0.3:0.3` at exposure `0.6`, realizing
30% / 30% / 40% unassigned. If fractions are oversubscribed (sum above one),
they are normalized proportionally within `traffic_allocation`; this keeps
the conversion total for every schema-valid vector. Zero weights are
omitted, matching `bucketSubject()`'s established exclusion semantics.
## Parameters
### experiment
[`CanonicalExperimentAllocation`](/docs/api/interfaces/canonicalexperimentallocation/)
## Returns
[`BasicBucketerExperiment`](/docs/api/interfaces/basicbucketerexperiment/)
# BannerComponent
> **BannerComponent**(`__namedParameters`): `any`
Full-width sticky banner placement.
Renders at the top or bottom of the viewport with a header, body,
CTA button, and optional dismiss control.
**Content fields used:** `header`, `body`, `message`, `cta_label`, `position`, `dismissible`
## Parameters
### \_\_namedParameters
[`BannerComponentProps`](/docs/api/type-aliases/bannercomponentprops/)
## Returns
`any`
## Example
```tsx
navigate('/plans')}
onDismiss={() => {}}
visible={true}
/>
```
# BannerFrame
> **BannerFrame**\<`C`\>(`__namedParameters`): `any`
Sticky banner component that handles:
- Top/bottom position
- Dismiss state + callback
- Visibility reset when parent re-shows
- Theme-aware default styles
Extend by wrapping and injecting `children` for custom body content.
## Type Parameters
### C
`C` *extends* [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/) = [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
## Parameters
### \_\_namedParameters
`BannerFrameProps`\<`C`\>
## Returns
`any`
## Example
```tsx
function TrialBanner(props: PlacementSlotProps) {
return (
🔥 {props.content.days_remaining} days left in your trial
);
}
```
# baseThemeForScheme
> **baseThemeForScheme**(`scheme`): `Readonly`\<[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)\>
The base palette for a resolved scheme.
## Parameters
### scheme
`"light"` \| `"dark"`
`'light'` or `'dark'`. Resolve `'system'` before calling.
## Returns
`Readonly`\<[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)\>
The matching base theme.
# bridgeUiPathResolversIntoRegistry
> **bridgeUiPathResolversIntoRegistry**(`resolvers`, `registry?`): () => `void`
Bridge an SDK-init `uiPathResolvers` map into a [CtaResolverRegistry](/docs/api/classes/ctaresolverregistry/)
so resolvers supplied at init actually dispatch on CTA clicks (plan 174
TASK-1 / F-70). Called by the SDK constructor after the init-time coverage
check runs.
Explicit registrations take precedence: an action type already present in
the registry (via [registerCtaResolver](/docs/api/functions/registerctaresolver/) or a direct `register`) is
left untouched, and a later explicit registration replaces the bridged
entry through the registry's normal override semantics. Each bridged
resolver receives the placement's raw authored `cta_path` record — the same
shape init-time validation checked. A rejected async resolver is logged,
never thrown into the click handler.
Returns a cleanup function that unregisters exactly the entries this call
bridged, skipping any the customer has since replaced.
## Parameters
### resolvers
`Record`\<`string`, (`uiPath`) => `void` \| `Promise`\<`void`\>\>
### registry?
[`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/) = `...`
## Returns
() => `void`
# bucketSubject
> **bucketSubject**(`subject`, `experimentHandle`, `experiment`): `string` \| `undefined`
Assign one subject to one experiment, or `undefined` for not-enrolled.
Exported for testing and for callers who want the decision without the
provider wrapper. Pure: same inputs always give the same arm, with no
storage, no clock and no randomness involved.
## Parameters
### subject
`string`
### experimentHandle
`string`
### experiment
[`BasicBucketerExperiment`](/docs/api/interfaces/basicbucketerexperiment/)
## Returns
`string` \| `undefined`
# buildControlPlaneEvent
> **buildControlPlaneEvent**(`eventType`, `payload?`): [`ControlPlaneEmitInput`](/docs/api/interfaces/controlplaneemitinput/)
Build the `(eventName, properties)` pair for a control-plane semantic event,
stamping the canonical source classification onto the payload.
Pure — used both by the browser SDK's
[RevTurbineCustomerSdk.trackControlPlaneEvent](/docs/api/classes/revturbinecustomersdk/#trackcontrolplaneevent) and by server/CLI
emitters that POST the same shape to the ingest endpoint.
## Parameters
### eventType
`"web_signed_up"` \| `"web_signed_in"` \| `"cli_signed_up"` \| `"cli_signed_in"` \| `"cli_command_executed"` \| `"playbook_version_submitted"` \| `"playbook_version_approved"` \| `"playbook_version_rejected"` \| `"playbook_version_deployed"` \| `"playbook_version_launched"` \| `"playbook_version_parked"` \| `"playbook_version_resumed"` \| `"playbook_version_discarded"` \| `"playbook_version_archived"` \| `"playbook_imported"` \| `"playbook_exported"` \| `"entity_created"` \| `"entity_updated"` \| `"entity_deleted"` \| `"web_api_error"`
A canonical control-plane event type.
### payload?
`Record`\<`string`, `JsonValue`\> = `{}`
Optional event-specific properties (e.g. `{ resource, resource_id }`).
## Returns
[`ControlPlaneEmitInput`](/docs/api/interfaces/controlplaneemitinput/)
The emit input to hand to the SDK's `capture` path.
# ButtonComponent
> **ButtonComponent**(`__namedParameters`): `any`
Standalone CTA button placement.
Renders a single styled button suitable for persistent nav CTAs
or inline upgrade prompts.
**Content fields used:** `cta_label`, `style`
## Parameters
### \_\_namedParameters
[`ButtonComponentProps`](/docs/api/type-aliases/buttoncomponentprops/)
## Returns
`any`
# categorizeActionError
> **categorizeActionError**(`err`): [`ActionErrorCategory`](/docs/api/type-aliases/actionerrorcategory/)
Bucket an unknown thrown value into a [ActionErrorCategory](/docs/api/type-aliases/actionerrorcategory/) without
surfacing its message (plan 144 TASK-14 — non-sensitive categories only).
## Parameters
### err
`unknown`
## Returns
[`ActionErrorCategory`](/docs/api/type-aliases/actionerrorcategory/)
# clearPersistedTheme
> **clearPersistedTheme**(`tenantId`, `storage?`): `void`
Clear persisted theme (useful on identity reset).
## Parameters
### tenantId
`string`
### storage?
`RevTurbineStorage`
## Returns
`void`
# collectAssignmentFactDeclarations
> **collectAssignmentFactDeclarations**(`sources`, `explicit?`): `ReadonlyMap`\<`string`, [`ExperimentAssignmentFactDeclaration`](/docs/api/interfaces/experimentassignmentfactdeclaration/)\>
Harvest valid assignment-fact declarations from providers exposing the
[ExperimentAssignmentFactMetadataCarrier](/docs/api/interfaces/experimentassignmentfactmetadatacarrier/) surface. Later sources win
on a duplicate handle, so pass the most authoritative source last.
## Parameters
### sources
readonly (`object` \| `undefined`)[]
### explicit?
readonly [`ExperimentAssignmentFactDeclaration`](/docs/api/interfaces/experimentassignmentfactdeclaration/)[]
## Returns
`ReadonlyMap`\<`string`, [`ExperimentAssignmentFactDeclaration`](/docs/api/interfaces/experimentassignmentfactdeclaration/)\>
# CompactCircularGauge
> **CompactCircularGauge**(`__namedParameters`): `any`
Minimal circular gauge for inline usage indicators.
Can be used standalone or as part of a `QuotaMeterFrame`.
## Parameters
### \_\_namedParameters
`CompactCircularGaugeProps`
## Returns
`any`
# composeEffectiveExperimentContext
> **composeEffectiveExperimentContext**\<`TContext`\>(`callerContext`, `providerState`, `contextRevision`): `EffectiveUserContextResolution` & `object`
Materialize one ephemeral user context from caller context and normalized
provider outcomes. The caller-owned object is never mutated.
## Type Parameters
### TContext
`TContext` *extends* `UserContextSnapshot`
## Parameters
### callerContext
`Readonly`\<`TContext`\>
### providerState
`ExperimentProviderState` \| `undefined`
### contextRevision
`string`
## Returns
`EffectiveUserContextResolution` & `object`
# createAnalyticsProvider
> **createAnalyticsProvider**(`options`): `EventConsumerProvider`
Create an analytics domain provider that forwards SDK events
(impressions, interactions, triggers, page views, etc.) to a
third-party analytics platform.
The returned provider should be passed in the `domainProviders`
array when calling `initRevTurbine()`.
## Parameters
### options
[`AnalyticsProviderOptions`](/docs/api/interfaces/analyticsprovideroptions/)
## Returns
`EventConsumerProvider`
## Examples
```ts
const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
window.analytics.track(eventName, properties);
},
});
```
```ts
const analytics = createAnalyticsProvider({
handler: (eventName, properties) => {
heap.track(eventName, properties);
},
filter: ['placement_interaction', 'placement_dismissed', 'placement_converted'],
});
```
```ts
const analytics = createAnalyticsProvider({
handler: (name, props) => posthog.capture(name, props),
transform: (name, props) => ({
eventName: `revturbine.${name}`,
properties: { ...props, source: 'revturbine-sdk' },
}),
});
```
# createBasicExperimentProvider
> **createBasicExperimentProvider**(`options`): `ExperimentAssignmentProvider`
Build the built-in bucketer. Register it explicitly to switch it on:
```ts
initRevTurbine({
domainProviders: [
createBasicExperimentProvider({
subject: () => currentUserId,
experiments: { pricing_test: { variants: ['control', 'variant_b'] } },
}),
],
});
```
## Parameters
### options
`BasicBucketerOptions`
## Returns
`ExperimentAssignmentProvider`
# createChainedPlacementRequest
> **createChainedPlacementRequest**(`placementHandle`, `options?`): `RevTurbinePlacementRequestConfig`
Create a typed placement request for chained/CTA follow-up lookups.
## Parameters
### placementHandle
`string`
### options?
[`RevTurbineChainedPlacementRequestOptions`](/docs/api/interfaces/revturbinechainedplacementrequestoptions/) = `{}`
## Returns
`RevTurbinePlacementRequestConfig`
# createCompositeExperimentProvider
> **createCompositeExperimentProvider**(`providers`, `options?`): `ExperimentAssignmentProvider`
Combine customer-owned experiment adapters behind the registry's single
`experiments` domain. Overlapping declared ownership is rejected for the
affected handles instead of allowing registration order to choose a winner.
## Parameters
### providers
readonly `ExperimentAssignmentProvider`[]
### options?
[`CompositeExperimentProviderOptions`](/docs/api/interfaces/compositeexperimentprovideroptions/) = `{}`
## Returns
`ExperimentAssignmentProvider`
# createCustomEndpointRuntimeConfig
> **createCustomEndpointRuntimeConfig**(`options`): `RevTurbineInitOptions`
Build a custom-endpoint SDK config.
## Parameters
### options
[`RevTurbineInitBaseOptions`](/docs/api/type-aliases/revturbineinitbaseoptions/) & `object`
## Returns
`RevTurbineInitOptions`
# createEntitlementPlacementRequest
> **createEntitlementPlacementRequest**(`entitlementHandle`, `options?`): `RevTurbinePlacementRequestConfig`
Create a typed placement request for entitlement-gated lookups.
## Parameters
### entitlementHandle
`string`
### options?
[`RevTurbineEntitlementPlacementRequestOptions`](/docs/api/interfaces/revturbineentitlementplacementrequestoptions/) = `{}`
## Returns
`RevTurbinePlacementRequestConfig`
# createLocalRuntimeConfig
## Call Signature
> **createLocalRuntimeConfig**\<`TUiPaths`\>(`options`): `RevTurbineInitOptions`
Build a local-only SDK config.
### Type Parameters
#### TUiPaths
`TUiPaths` *extends* readonly `unknown`[]
### Parameters
#### options
[`RevTurbineInitBaseOptions`](/docs/api/type-aliases/revturbineinitbaseoptions/) & `object`
### Returns
`RevTurbineInitOptions`
## Call Signature
> **createLocalRuntimeConfig**(`options`): `RevTurbineInitOptions`
Build a local-only SDK config.
### Parameters
#### options
[`RevTurbineInitBaseOptions`](/docs/api/type-aliases/revturbineinitbaseoptions/) & `object`
### Returns
`RevTurbineInitOptions`
# createNativeExperimentAssignmentProvider
> **createNativeExperimentAssignmentProvider**(`options`): `ExperimentAssignmentProvider` & [`ExperimentAssignmentFactMetadataCarrier`](/docs/api/interfaces/experimentassignmentfactmetadatacarrier/)
Build the native assignment provider whose allocation authority is the
canonical Experiment version rather than duplicated SDK configuration.
## Parameters
### options
[`NativeExperimentAssignmentOptions`](/docs/api/interfaces/nativeexperimentassignmentoptions/)
## Returns
`ExperimentAssignmentProvider` & [`ExperimentAssignmentFactMetadataCarrier`](/docs/api/interfaces/experimentassignmentfactmetadatacarrier/)
# createPostHogAnalyticsProvider
> **createPostHogAnalyticsProvider**(`options`): `EventConsumerProvider`
Create an analytics resolver that forwards SDK events to PostHog (plan 112).
A thin PostHog-typed convenience over [createAnalyticsProvider](/docs/api/functions/createanalyticsprovider/): each
SDK event becomes `posthog.capture(eventName, properties)`. Pass the result
in the `domainProviders` array at init so the same semantic events feed both
the first-party ingest pipeline and PostHog.
Like [createAnalyticsProvider](/docs/api/functions/createanalyticsprovider/), a throw from `posthog.capture` is
swallowed — a PostHog failure never crashes the SDK event pipeline.
## Parameters
### options
[`PostHogAnalyticsProviderOptions`](/docs/api/interfaces/posthoganalyticsprovideroptions/)
## Returns
`EventConsumerProvider`
## Example
```ts
import posthog from 'posthog-js';
import { createPostHogAnalyticsProvider, initRevTurbine } from '@revturbine/sdk';
const analytics = createPostHogAnalyticsProvider({ posthog });
const session = await initRevTurbine({
tenantId: 'tenant_abc',
publicKey: 'rtk_…',
endpoint: 'https://edge.example.com',
mode: 'snippet',
domainProviders: [analytics],
});
```
# createPostHogIntegration
> **createPostHogIntegration**(`options`): `EventConsumerProvider`
Create a PostHog integration with an optional identity lifecycle (plan 144
TASK-17 / REQ-26).
A superset of [createPostHogAnalyticsProvider](/docs/api/functions/createposthoganalyticsprovider/): every event is still
`posthog.capture(name, properties)` with the **same `filter` / `transform`
semantics** (REQ-27), and — when the matching flag is on — RevTurbine's
identify/logout, account grouping, and navigation are mirrored to PostHog's
native `identify` / `reset` / `group` / `$pageview`. All sync flags default
`false`, so by default this behaves exactly like the capture-only provider.
No PostHog package is imported or bundled — the client is injected — so this
code is tree-shakable and only pulled in when you import it (AC-20). A throw
from any PostHog call is swallowed and never blocks RevTurbine ingest (AC-17).
`createPostHogAnalyticsProvider` remains exported for existing integrations.
## Parameters
### options
[`PostHogIntegrationOptions`](/docs/api/interfaces/posthogintegrationoptions/)
## Returns
`EventConsumerProvider`
# createRevTurbineApiClient
> **createRevTurbineApiClient**(`config`): `Client`
Create a configured RevTurbine API client instance.
## Parameters
### config
`RevTurbineApiClientConfig`
Client configuration.
## Returns
`Client`
Typed API client with GET, POST, PUT, PATCH, DELETE methods.
# createSemanticEvent
> **createSemanticEvent**(`eventType`, `payload`, `options?`): [`RevTurbineSemanticEvent`](/docs/api/interfaces/revturbinesemanticevent/)
Create a typed semantic event payload.
## Parameters
### eventType
`string`
### payload
[`SdkEventProperties`](/docs/api/type-aliases/sdkeventproperties/)
### options?
`RevTurbineEventOptions`
## Returns
[`RevTurbineSemanticEvent`](/docs/api/interfaces/revturbinesemanticevent/)
# createServerRuntimeConfig
> **createServerRuntimeConfig**(`options`): `RevTurbineInitOptions`
Build a server-backed SDK config (default runtime mode).
## Parameters
### options
[`RevTurbineInitBaseOptions`](/docs/api/type-aliases/revturbineinitbaseoptions/)
## Returns
`RevTurbineInitOptions`
# createSlotPlacementRequest
> **createSlotPlacementRequest**(`slotId`, `componentType`, `options?`): `RevTurbinePlacementRequestConfig`
Create a typed placement request for slot + surface lookups.
## Parameters
### slotId
`string`
### componentType
`"inline"` \| `"custom"` \| `"push"` \| `"banner"` \| `"modal"` \| `"tooltip"` \| `"sidebar"` \| `"toast"` \| `"fullscreen"` \| `"email"` \| `"sms"` \| `"in_page"` \| `"button"` \| `"full_page"` \| `"agent"` \| `"cli"`
### options?
[`RevTurbineSlotPlacementRequestOptions`](/docs/api/interfaces/revturbineslotplacementrequestoptions/) = `{}`
## Returns
`RevTurbinePlacementRequestConfig`
# createStrictLocalRuntimeConfig
> **createStrictLocalRuntimeConfig**\<`TUiPaths`\>(`options`): `RevTurbineInitOptions`
Build a strict local-only SDK config.
Unlike [createLocalRuntimeConfig](/docs/api/functions/createlocalruntimeconfig/), this helper always requires
`exportedConfig.content_ui_paths` and complete `uiPathResolvers` coverage at
compile time for action types present in that Playbook.
## Type Parameters
### TUiPaths
`TUiPaths` *extends* readonly `unknown`[]
## Parameters
### options
[`RevTurbineInitBaseOptions`](/docs/api/type-aliases/revturbineinitbaseoptions/) & `object`
## Returns
`RevTurbineInitOptions`
# createTreatmentInteraction
> **createTreatmentInteraction**(`userId`, `placementId`, `interactionType`, `options?`): `RevTurbineTreatmentInteractionInput`
Create a typed treatment interaction payload.
## Parameters
### userId
`string`
### placementId
`string`
### interactionType
`RevTurbineTreatmentInteractionType`
### options?
[`RevTurbineTreatmentInteractionOptions`](/docs/api/interfaces/revturbinetreatmentinteractionoptions/) = `{}`
## Returns
`RevTurbineTreatmentInteractionInput`
# currentBrowserRoute
> **currentBrowserRoute**(): `string` \| `null`
The browser's current route — `window.location.pathname`, normalized.
`null` outside a browser (SSR, server runtimes, workers without `location`).
## Returns
`string` \| `null`
# defineUiPathResolvers
> **defineUiPathResolvers**\<`TUiPaths`\>(`uiPaths`, `resolvers`): [`RevTurbineUiPathResolverMap`](/docs/api/type-aliases/revturbineuipathresolvermap/)
Compile-time helper for authoring complete UI-path resolver maps.
When `uiPaths` is provided as a `const` array, TypeScript enforces that
`resolvers` contains every `action_type` key.
## Type Parameters
### TUiPaths
`TUiPaths` *extends* readonly `unknown`[]
## Parameters
### uiPaths
`TUiPaths`
### resolvers
[`RevTurbineRequiredUiPathResolvers`](/docs/api/type-aliases/revturbinerequireduipathresolvers/)\<`TUiPaths`\> & [`RevTurbineUiPathResolverMap`](/docs/api/type-aliases/revturbineuipathresolvermap/)
## Returns
[`RevTurbineUiPathResolverMap`](/docs/api/type-aliases/revturbineuipathresolvermap/)
# dispatchCtaClick
> **dispatchCtaClick**(`uiPath`, `context`, `resolvers`, `fallback?`): `boolean`
Dispatch a CTA activation: invoke the registered resolver for the action
type if one exists, otherwise fall back to the supplied callback.
Returns `true` when a registered resolver handled the click, `false` when it
fell through to `fallback`. A registered resolver fully handles the action —
the fallback is not also called.
## Parameters
### uiPath
[`PlacementUiPath`](/docs/api/interfaces/placementuipath/)
### context
[`CtaResolverContext`](/docs/api/interfaces/ctaresolvercontext/)
### resolvers
[`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/)
### fallback?
(`uiPath`) => `void`
## Returns
`boolean`
# EmailPreviewComponent
> **EmailPreviewComponent**(`__namedParameters`): `any`
Static email preview: envelope card with a subject header and body, plus the
CTA row.
**Content fields used:** `subject`, `body`, `cta_label`, `secondary_cta_label`
## Parameters
### \_\_namedParameters
[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)
## Returns
`any`
# EngagementArea
> **EngagementArea**(`__namedParameters`): `ReactElement`
A qualified engagement region (plan 144 TASK-13 / REQ-21). It:
- establishes a [TelemetryScope](/docs/api/functions/telemetryscope/) (`area` / `purpose`) for its descendants;
- emits `viewEvent` **once** when first viewport-qualified — one-shot across
React Strict Mode's double-mount (AC-12);
- accrues dwell only while the region is **onscreen and the tab is visible**,
emitting `dwellEvent` with `dwell_ms` when it unmounts;
- bubbles descendant clicks as `interactionEvent`.
Renders its children and never throws without a provider (AC-13).
## Parameters
### \_\_namedParameters
[`EngagementAreaProps`](/docs/api/interfaces/engagementareaprops/)
## Returns
`ReactElement`
# entitlementResultDenies
> **entitlementResultDenies**(`result`): `boolean`
Whether an entitlement result denies access.
A result denies when its `status` is `'denied'` OR the evaluator's `allowed`
verdict is explicitly `false`. The second clause matters for at-cap limit
rules: blocking enforcement — including the unset-enforcement default —
resolves `{ status: 'limited', allowed: false }`, and gating on status alone
silently granted access at the cap (plan 179 TASK-10; the cold-funnel
"cap never blocks" trap). Degrade mode (`limited` + `allowed: true`) stays
granted.
## Parameters
### result
\{ \} \| `null`
## Returns
`boolean`
# fallbackAccountId
> **fallbackAccountId**(`userId`): `string`
Build the fallback account key for a user id — [FALLBACK\_ACCOUNT\_ID\_PREFIX](/docs/api/variables/fallback_account_id_prefix/) followed by the id verbatim.
The id is used exactly as given: redaction and trimming belong to the caller,
which already resolved the `user_id` it is about to put on the same wire row.
Deriving the key here from a *different* normalization is how the two lanes
would stop joining.
## Parameters
### userId
`string`
The user id (or anonymous id) the event is attributed to,
already normalized/redacted exactly as it will appear in `user_id`.
## Returns
`string`
The prefixed fallback account key.
# FixedSurfaceSlot
> **FixedSurfaceSlot**(`__namedParameters`): `any`
Fixed surface slot — always renders an embedded placement.
Use for inline placements that should always be present on the page:
upgrade banners, quota meters, promo cards, embedded CTAs, etc.
The slot loads automatically on mount. If no placement matches,
`fallback` is rendered so the page never has an empty gap.
## Parameters
### \_\_namedParameters
`FixedSurfaceSlotProps`
## Returns
`any`
## Example
```tsx
}
/>
```
# formatCurrencyMinorUnits
> **formatCurrencyMinorUnits**(`amount`, `currencyCode`, `locale?`): `string`
Format an integer minor-unit amount for Playbook-backed display tokens.
## Parameters
### amount
`number`
### currencyCode
`string`
### locale?
`string`
## Returns
`string`
# Gate
> **Gate**(`__namedParameters`): `any`
Access-gate surface slot — renders children when entitled (allowed *or*
`limited`), or a gated placement when access is `denied`.
Checks entitlements and/or usage thresholds. On denial, displays the configured
gated placement (or `deniedFallback` if no placement matches). On success,
renders `children` — or `limitedFallback` when the entitlement is `limited`.
## Parameters
### \_\_namedParameters
`AccessGateSurfaceSlotProps`
## Returns
`any`
## Example
```tsx
// `can` shorthand — mirrors useCan('mp4_download')
Upgrade to export}>
// soft-warn while still granting access when the balance runs low
}>
}
>
```
# getDefaultCtaResolverRegistry
> **getDefaultCtaResolverRegistry**(): [`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/)
Get or create the default global CTA resolver registry. The
[PlacementRenderer](/docs/api/functions/placementrenderer/) uses this when no `ctaResolvers` prop is supplied.
## Returns
[`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/)
# getDefaultRegistry
> **getDefaultRegistry**(): [`PlacementTypeRegistry`](/docs/api/classes/placementtyperegistry/)
Get or create the default global placement type registry.
On the React entry the registry comes pre-seeded with the built-in slot
types; on the headless entry it starts empty (headless consumers register
their own types via [PlacementTypeRegistry.register](/docs/api/classes/placementtyperegistry/#register)).
## Returns
[`PlacementTypeRegistry`](/docs/api/classes/placementtyperegistry/)
# InitFailureDiagnostic
> **InitFailureDiagnostic**(`__namedParameters`): `any`
Render a development-only banner describing a failed SDK initialization.
Returns `null` for a healthy status, so the caller can render it
unconditionally in development without branching.
## Parameters
### \_\_namedParameters
[`InitFailureDiagnosticProps`](/docs/api/interfaces/initfailurediagnosticprops/)
## Returns
`any`
# initRevTurbine
> **initRevTurbine**\<`TUser`\>(`options`): `Promise`\<[`SdkSession`](/docs/api/classes/sdksession/)\>
Create a fully-initialized SDK session.
This is the recommended entry point for headless (non-React) consumers.
It handles:
1. SDK initialization via `initRevTurbine()`
2. User identification (if `options.user.id` is provided)
3. Theme resolution (from RevTurbineConfig or API)
4. Placement bootstrapping (preloading decisions)
The returned [SdkSession](/docs/api/classes/sdksession/) exposes the full imperative API:
user context management, placement controllers, entitlement gates,
and event tracking.
## Type Parameters
### TUser
`TUser` *extends* [`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/) = [`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)
## Parameters
### options
[`SdkSessionOptions`](/docs/api/type-aliases/sdksessionoptions/) & `object`
## Returns
`Promise`\<[`SdkSession`](/docs/api/classes/sdksession/)\>
## Example
```ts
const session = await initRevTurbine({
tenantId: 'tenant_abc',
publicKey: 'rtk_…',
endpoint: 'https://edge.example.com',
mode: 'snippet',
user: { id: 'user_123', plan_handle: 'pro' },
bootstrapPlacements: [
{ placement: { name: 'pricing_banner' } },
],
});
```
# initStatusForError
> **initStatusForError**(`phase`, `error`): [`RevTurbineInitStatus`](/docs/api/interfaces/revturbineinitstatus/)
Build the failure status for a thrown init error.
## Parameters
### phase
[`RevTurbineInitPhase`](/docs/api/type-aliases/revturbineinitphase/)
The phase that was running when the error was thrown.
### error
`unknown`
The thrown value; non-Errors are stringified.
## Returns
[`RevTurbineInitStatus`](/docs/api/interfaces/revturbineinitstatus/)
A fully-populated failure status.
# InlineCardPlacement
> **InlineCardPlacement**\<`C`\>(`__namedParameters`): `any`
Abstract inline card/embed placement rendered within the page flow.
Handles:
- Visibility gating
- Theme-aware card container with border, radius, padding
- Optional image, header, body, and CTA
- `data-rt-placement="inline-embed"` attribute
Extend by wrapping and injecting `children` for custom card bodies.
## Type Parameters
### C
`C` *extends* [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/) = [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
## Parameters
### \_\_namedParameters
`InlineCardPlacementProps`\<`C`\>
## Returns
`any`
## Example
```tsx
function FeatureCallout(props: PlacementSlotProps) {
return (
);
}
```
# InlineEmbedComponent
> **InlineEmbedComponent**(`__namedParameters`): `any`
Inline card/embed placement rendered within the page flow.
Lightweight card with optional image, header, body, and CTA button.
Designed to sit naturally within page content.
**Content fields used:** `header`, `body`, `message`, `image_url`, `cta_label`
## Parameters
### \_\_namedParameters
[`InlineEmbedComponentProps`](/docs/api/type-aliases/inlineembedcomponentprops/)
## Returns
`any`
# isExperimentAssignmentFactDeclaration
> **isExperimentAssignmentFactDeclaration**(`value`): `value is ExperimentAssignmentFactDeclaration`
Defensively validate one declaration from a plain-JS caller. The SDK is
exposed as `window.RevTurbine`, so shapes are checked, never assumed.
## Parameters
### value
`unknown`
## Returns
`value is ExperimentAssignmentFactDeclaration`
# isFallbackAccountId
> **isFallbackAccountId**(`accountId`): `boolean`
Whether an `account_id` is one the SDK fabricated from a user id rather than
one the integration identified.
Use it to keep fabricated keys out of account denominators, exactly as the
warehouse read-time guards do. `null`, `undefined`, and the bare prefix with
nothing after it are **not** fallback keys — the first two are absence, and
the third is not a key at all.
## Parameters
### accountId
`string` \| `null` \| `undefined`
The account id to classify.
## Returns
`boolean`
`true` only for a non-empty id carrying the fallback prefix.
# isIdentifierLikeSegment
> **isIdentifierLikeSegment**(`segment`): `boolean`
`true` when a single path segment looks like an identifier or personal data
rather than a stable route name. Exported for tests and custom resolvers.
## Parameters
### segment
`string`
## Returns
`boolean`
# loadTheme
> **loadTheme**(`opts`, `onUpdate?`): `Promise`\<[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)\>
Load the tenant's theme:
1. Return the locally-persisted theme immediately (fast path).
2. Fetch the latest from the API in the background.
3. If the remote version differs, update local storage and return the new theme.
Returns a resolved [RevTurbineTheme](/docs/api/interfaces/revturbinetheme/) (merged with defaults).
Also provides an `onUpdate` callback callers can use to react to
background refreshes.
## Parameters
### opts
`ThemeLoaderOptions`
### onUpdate?
(`theme`) => `void`
## Returns
`Promise`\<[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)\>
# mergeTheme
> **mergeTheme**(`input?`, `base?`): [`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)
Deep-merge a partial theme input with a base theme, producing a complete
[RevTurbineTheme](/docs/api/interfaces/revturbinetheme/).
## Parameters
### input?
[`RevTurbineThemeInput`](/docs/api/type-aliases/revturbinethemeinput/) \| `null`
Partial tokens. Every omitted token falls back to `base`.
### base?
`Readonly`\<[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)\> = `DEFAULT_THEME`
The palette to merge over. Defaults to [DEFAULT\_THEME](/docs/api/variables/default_theme/);
pass [DARK\_THEME](/docs/api/variables/dark_theme/) (or use [baseThemeForScheme](/docs/api/functions/basethemeforscheme/)) to resolve a
partial brand theme against the dark palette instead.
## Returns
[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)
# ModalComponent
> **ModalComponent**(`__namedParameters`): `any`
Centered modal overlay placement.
Supports optional and blocking modes. Optional modals can be
dismissed via ESC key, close button, or overlay click. Blocking modals
require CTA interaction.
**Content fields used:** `header`, `body`, `image_url`, `cta_label`, `secondary_cta_label`, `style`
**Accessibility:** `role="dialog"`, `aria-modal="true"`, keyboard trap
## Parameters
### \_\_namedParameters
[`ModalComponentProps`](/docs/api/type-aliases/modalcomponentprops/)
## Returns
`any`
# ModalFrame
> **ModalFrame**\<`C`\>(`__namedParameters`): `any`
Modal overlay component that handles:
- Backdrop click dismiss (optional mode)
- ESC key dismiss (optional mode)
- Focus trap on the dialog
- Visibility + dismissed state management
- Theme-aware default styles
Extend by wrapping and injecting `children` for custom body content.
## Type Parameters
### C
`C` *extends* [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/) = [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
## Parameters
### \_\_namedParameters
`ModalFrameProps`\<`C`\>
## Returns
`any`
## Example
```tsx
function MyModal(props: PlacementSlotProps) {
return (
);
}
```
# normalizeConfigArtifactOrThrow
> **normalizeConfigArtifactOrThrow**(`raw`, `source`, `targetDefaults?`): \{ \} \| `undefined`
Parse either supported wire shape into the canonical Playbook shape.
Presence of either canonical discriminator selects the canonical parser, so
an unsupported future `format_version` can never fall back to legacy.
## Parameters
### raw
`unknown`
### source
`string`
### targetDefaults?
[`ConfigTargetDefaults`](/docs/api/interfaces/configtargetdefaults/)
## Returns
\{ \} \| `undefined`
# normalizeSlotRoute
> **normalizeSlotRoute**(`input`): `string` \| `null`
Normalize a path, URL or route pattern into a slot route.
Strips any origin, query string and fragment; collapses duplicate slashes
and a trailing slash; keeps route-pattern segments (`[id]`, `:id`)
verbatim; templates identifier-like segments to `:id`; bounds the result to
[SLOT\_ROUTE\_MAX\_LENGTH](/docs/api/variables/slot_route_max_length/). Returns `null` for empty or non-string input.
## Parameters
### input
`string` \| `null` \| `undefined`
## Returns
`string` \| `null`
## Example
```ts
normalizeSlotRoute('https://app.example.com/accounts/4821/billing?tab=x#top');
// → '/accounts/:id/billing'
normalizeSlotRoute('/projects/[projectId]');
// → '/projects/[projectId]'
```
# parsePromotion
> **parsePromotion**(`raw?`): \{ \} \| `undefined`
Parse a promotion record from a placement output.
## Parameters
### raw?
`Record`\<`string`, `unknown`\>
## Returns
\{ \} \| `undefined`
# parseUiPath
> **parseUiPath**(`raw`): [`PlacementUiPath`](/docs/api/interfaces/placementuipath/)
Parse a `cta_path` (or legacy `ui_path`) record from a placement output into
a typed [PlacementUiPath](/docs/api/interfaces/placementuipath/).
Built-in fields (`url`, `plan_handle`, …) are lifted onto typed properties.
The `type` is preserved verbatim for any non-empty string — including
tenant-defined custom action names — so a registered [CtaResolver](/docs/api/type-aliases/ctaresolver/) can
key on it; only an absent or non-string `type` defaults to `'dismiss'`. Every
remaining key is collected into `params` for custom resolvers to read.
## Parameters
### raw
`Record`\<`string`, `unknown`\>
## Returns
[`PlacementUiPath`](/docs/api/interfaces/placementuipath/)
# Placement
> **Placement**(`__namedParameters`): `any`
Render-prop component for placement integration.
An alternative to [usePlacement](/docs/api/functions/useplacement/) for class components or when
render-prop composition is preferred.
## Parameters
### \_\_namedParameters
[`PlacementProps`](/docs/api/type-aliases/placementprops/)
## Returns
`any`
## Example
```tsx
{({ visible, content, ctaClick }) =>
visible ? (
{content?.header}
) : null
}
```
# PlacementDecisionInspector
> **PlacementDecisionInspector**(`__namedParameters`): `any`
Visual debug panel that explains why a placement decision was selected.
Includes segment predicate evaluations, entitlement-rule matching,
final decision metadata, and policy/targeting snapshots.
## Parameters
### \_\_namedParameters
[`PlacementDecisionInspectorProps`](/docs/api/interfaces/placementdecisioninspectorprops/)
## Returns
`any`
# PlacementRenderer
> **PlacementRenderer**(`__namedParameters`): `any`
Resolves a placement output to the appropriate registered slot type
and renders it with resolved content.
This is the primary component customers use to render placements.
It handles:
- Slot type resolution from the registry
- Personalization token expansion
- UI path and promotion parsing
- CTA and dismiss callback wiring
## Parameters
### \_\_namedParameters
`PlacementRendererProps`
## Returns
`any`
# PushPreviewComponent
> **PushPreviewComponent**(`__namedParameters`): `any`
Static push-notification preview: a notification card with a title and body,
plus the CTA row.
**Content fields used:** `header`/`title`, `body`, `cta_label`, `secondary_cta_label`
## Parameters
### \_\_namedParameters
[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)
## Returns
`any`
# QuotaMeterFrame
> **QuotaMeterFrame**\<`C`\>(`__namedParameters`): `any`
Quota/usage meter with multiple display variants.
Handles:
- Progress bar, circular gauge, or numeric counter rendering
- Threshold-based visibility (`showAtPercent`)
- Typed usage props override content fields
- `data-rt-placement="quota-meter"` attribute
Extend by wrapping and injecting `children` for custom content below the meter.
## Type Parameters
### C
`C` *extends* [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/) = [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
## Parameters
### \_\_namedParameters
`QuotaMeterFrameProps`\<`C`\>
## Returns
`any`
## Example
```tsx
function MinutesMeter(props: PlacementSlotProps) {
const { minutesUsed, minutesLimit } = useUsage();
return (
);
}
```
# registerBuiltinSlotTypes
> **registerBuiltinSlotTypes**(`registry`): `void`
Register all built-in slot types on the given registry.
Built-in types cover the core surface types:
- banner → BannerSlot (full-width top/bottom)
- modal → ModalSlot (overlay dialog, optional/blocking)
- in_page → InPageSlot (card/embed in page flow)
- toast → ToastSlot (ephemeral notification)
- button → ButtonSlot (nav bar / CTA button)
- full_page → FullPageSlot (dedicated managed page)
- email/sms/push → channel previews (static out-of-band mocks)
Additional specialized types registered as in_page variants:
- quota_meter → QuotaMeterSlot (usage meter + upgrade CTA)
## Parameters
### registry
[`PlacementTypeRegistry`](/docs/api/classes/placementtyperegistry/)
## Returns
`void`
# registerBuiltinSnoozeResolver
> **registerBuiltinSnoozeResolver**(`snooze`, `registry?`): () => `void`
Register the built-in `snooze` CTA resolver (plan 174 TASK-6 companion /
Q-5): an authored `snooze` ui-path dispatches to the SDK's remind-later
verb, honoring a payload-authored `remind_later_minutes` window when the
placement output carries one (plan 167; the verb's default matches the
60-minute tenant default). Called by the SDK constructor AFTER the init
`uiPathResolvers` bridge, and skips registration when a `snooze` resolver
already exists — so a customer's resolver (init map or explicit
registration, before or after init) always wins. Returns a cleanup that
unregisters only this entry.
## Parameters
### snooze
(`outputId`, `seconds?`) => `void`
### registry?
[`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/) = `...`
## Returns
() => `void`
# registerCtaResolver
> **registerCtaResolver**(`type`, `resolver`): `void`
Register a [CtaResolver](/docs/api/type-aliases/ctaresolver/) on the default global registry.
This is the ergonomic entry point for customers:
```ts
registerCtaResolver('connect_crm', (uiPath) => {
openCrmModal(uiPath.url, uiPath.params);
});
```
## Parameters
### type
`string`
### resolver
[`CtaResolver`](/docs/api/type-aliases/ctaresolver/)
## Returns
`void`
# registerServerActionResolvers
> **registerServerActionResolvers**(`actions`, `callbacks`, `registry?`): () => `void`
Register app-owned server actions behind the existing CTA registry.
Called after the init `uiPathResolvers` bridge and built-ins, so any resolver
the customer registered through those surfaces wins. A later explicit
`registerCtaResolver()` still replaces this entry normally. Handler failures
are contained, reported, and tracked without applying context.
## Parameters
### actions
[`ServerActionMap`](/docs/api/type-aliases/serveractionmap/)
### callbacks
`ServerActionResolverCallbacks`
### registry?
[`CtaResolverRegistry`](/docs/api/classes/ctaresolverregistry/) = `...`
## Returns
() => `void`
# remediationFor
> **remediationFor**(`message`): `string`
Resolve the remediation for a thrown init error.
## Parameters
### message
`string`
The error message as thrown.
## Returns
`string`
Actionable guidance. Never empty.
# resetDefaultCtaResolverRegistry
> **resetDefaultCtaResolverRegistry**(): `void`
Reset the default CTA resolver registry (useful for testing and for tearing
down tenant-specific resolvers).
## Returns
`void`
# resetDefaultRegistry
> **resetDefaultRegistry**(): `void`
Reset the default registry (useful for testing).
## Returns
`void`
# resolveBranding
> **resolveBranding**(`input?`): [`ResolvedBranding`](/docs/api/interfaces/resolvedbranding/)
Resolve branding down the four-rung ladder. Pure and synchronous: fetching the
Branding API (rung 3) is the host's job — pass the result as `apiBranding`.
## Parameters
### input?
[`BrandingResolutionInput`](/docs/api/interfaces/brandingresolutioninput/) = `{}`
The available branding sources; see [BrandingResolutionInput](/docs/api/interfaces/brandingresolutioninput/).
## Returns
[`ResolvedBranding`](/docs/api/interfaces/resolvedbranding/)
The merged branding and the rung it came from.
# resolveContent
> **resolveContent**(`content`, `context`): [`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
Resolve all personalization tokens in a content object.
## Parameters
### content
`Record`\<`string`, `unknown`\>
### context
[`PersonalizationContext`](/docs/api/interfaces/personalizationcontext/)
## Returns
[`ResolvedContent`](/docs/api/type-aliases/resolvedcontent/)
# resolveLocalPlaybook
> **resolveLocalPlaybook**(`localRuntime?`): [`ConfigArtifact`](/docs/api/type-aliases/configartifact/) \| [`UnvalidatedConfigArtifact`](/docs/api/type-aliases/unvalidatedconfigartifact/) \| `undefined`
Resolve the local-mode Playbook from either accepted key.
`playbook` is canonical; `exportedConfig` is the legacy alias kept for
backwards compatibility. Both are optional at the type level, so this is the
one place that decides precedence — consumers must not read either key
directly or they will disagree about which one wins.
The returned artifact may be the raw [UnvalidatedConfigArtifact](/docs/api/type-aliases/unvalidatedconfigartifact/) the
caller supplied (e.g. an imported JSON module); the runtime config pipeline
validates it via `configArtifactForRuntime` before evaluation.
## Parameters
### localRuntime?
`Pick`\<`RevTurbineLocalRuntimeOptions`, `"playbook"` \| `"exportedConfig"`\> \| `null`
## Returns
[`ConfigArtifact`](/docs/api/type-aliases/configartifact/) \| [`UnvalidatedConfigArtifact`](/docs/api/type-aliases/unvalidatedconfigartifact/) \| `undefined`
# resolvePersistentStorage
> **resolvePersistentStorage**(`storage?`): `RevTurbineStorage`
Resolve a persistent storage provider.
Priority:
1. Explicitly provided `storage` (customer override)
2. `localStorage` when running in a browser
3. In-memory fallback
## Parameters
### storage?
`RevTurbineStorage`
## Returns
`RevTurbineStorage`
# resolveSessionStorage
> **resolveSessionStorage**(`storage?`): `RevTurbineStorage`
Resolve a session-scoped storage provider.
Priority:
1. Explicitly provided `storage` (customer override)
2. `sessionStorage` when running in a browser
3. In-memory fallback
## Parameters
### storage?
`RevTurbineStorage`
## Returns
`RevTurbineStorage`
# resolveTokens
> **resolveTokens**(`template`, `context`): `string`
Resolve personalization tokens in a string value.
Tokens use the `{{token_name}}` format.
## Parameters
### template
`string`
### context
[`PersonalizationContext`](/docs/api/interfaces/personalizationcontext/)
## Returns
`string`
# RevTurbineProvider
> **RevTurbineProvider**\<`TUser`, `TOptions`\>(`__namedParameters`): `any`
React context provider for the RevTurbine SDK.
Wraps your application to provide SDK access to all child components.
Handles initialization, user identification, and optional placement bootstrap.
## Type Parameters
### TUser
`TUser` *extends* [`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/) = [`RevTurbineUserContext`](/docs/api/interfaces/revturbineusercontext/)
### TOptions
`TOptions` *extends* [`RevTurbineInitInputOptions`](/docs/api/type-aliases/revturbineinitinputoptions/) = [`RevTurbineInitInputOptions`](/docs/api/type-aliases/revturbineinitinputoptions/)
## Parameters
### \_\_namedParameters
`RevTurbineProviderProps`\<`TUser`, `TOptions`\>
## Returns
`any`
## Example
```tsx
```
# RevTurbineRoute
> **RevTurbineRoute**(`__namedParameters`): `ReactElement`
Declares the app route for the slots beneath it (BL-0207 / D-30). Every
`usePlacement()` / `` in the subtree stamps this route onto its slot
lifecycle events (`slot_evaluated`, `slot_filled`, …), and ingestion-driven
slot discovery persists it on the discovered surface slot.
Optional: without it, slots report `window.location.pathname` with
identifier-like segments templated to `:id`. Mount it when your router
knows the route PATTERN, so `/projects/42` and `/projects/43` are one route.
Renderless — adds no DOM node. The innermost boundary wins.
## Parameters
### \_\_namedParameters
[`RevTurbineRouteProps`](/docs/api/interfaces/revturbinerouteprops/)
## Returns
`ReactElement`
## Example
```tsx
// Next.js App Router (app/layout.tsx child, a client component)
const route = routePatternFromParams(usePathname(), useParams());
return {children};
```
# RevTurbineThemeProvider
> **RevTurbineThemeProvider**(`__namedParameters`): `any`
Provides the active [RevTurbineTheme](/docs/api/interfaces/revturbinetheme/) to all child SDK components.
Typically rendered internally by [RevTurbineProvider](/docs/api/functions/revturbineprovider/) — consumers
don't need to add this manually.
The theme is merged over [DEFAULT\_THEME](/docs/api/variables/default_theme/) here, so
[useRevTurbineTheme](/docs/api/functions/userevturbinetheme/) always returns a complete theme.
That merge used to be the caller's job while the prop was typed as a complete
`RevTurbineTheme`, which made a partial theme a runtime crash rather than a
type error: components read `theme.typography.fontSize` and got `undefined`
on a theme that only set colors. `RevTurbineThemeInput` has always documented
itself as "all values are optional — the SDK deep-merges with defaults"; this
is the component honouring that.
## Parameters
### \_\_namedParameters
`RevTurbineThemeProviderProps`
## Returns
`any`
# routePatternFromParams
> **routePatternFromParams**(`pathname`, `params`): `string` \| `null`
Rebuild the framework route PATTERN from a concrete pathname and the
framework's route params — the Next.js App Router recipe, without the SDK
importing `next/navigation`:
```tsx
'use client';
import { useParams, usePathname } from 'next/navigation';
import { RevTurbineRoute, routePatternFromParams } from '@revturbine/sdk';
export function RouteBoundary({ children }: { children: React.ReactNode }) {
const route = routePatternFromParams(usePathname(), useParams());
return {children};
}
```
`/projects/p_42/settings` with `{ projectId: 'p_42' }` becomes
`/projects/[projectId]/settings`; a catch-all `{ slug: ['a', 'b'] }`
becomes `[...slug]`. The result is then normalized like any other route,
so a segment the params did not name is still templated if it looks like
an identifier.
## Parameters
### pathname
`string` \| `null` \| `undefined`
### params
`Readonly`\<`Record`\<`string`, `string` \| readonly `string`[] \| `undefined`\>\> \| `null` \| `undefined`
## Returns
`string` \| `null`
# Slot
> **Slot**(`__namedParameters`): `any`
Universal surface slot component. Fetches a placement decision and
automatically renders it using the correct registered slot type.
Combines `usePlacement` (decision fetching + interaction tracking) with
`PlacementRenderer` (slot type resolution + rendering).
## Parameters
### \_\_namedParameters
`SurfaceSlotComponentProps`
## Returns
`any`
## Example
```tsx
handleCTA(uiPath)}
fallback={}
/>
```
# SmsPreviewComponent
> **SmsPreviewComponent**(`__namedParameters`): `any`
Static SMS preview: a chat bubble carrying the message body, plus the CTA
row (rendered below the bubble).
**Content fields used:** `body`, `message`, `cta_label`, `secondary_cta_label`
## Parameters
### \_\_namedParameters
[`PlacementSlotProps`](/docs/api/interfaces/placementslotprops/)
## Returns
`any`
# TelemetryScope
> **TelemetryScope**(`__namedParameters`): `ReactElement`
Establishes ambient telemetry context (`area` / `action` / `purpose`) for its
descendants (plan 144 TASK-12 / REQ-21). Nested scopes merge
**inner-over-outer**, and per-event [useTrack](/docs/api/functions/usetrack/) options override the
scope (the outer → inner → invocation precedence). It is renderless — renders
its children unchanged, adds no DOM node (REQ-19), and works with or without a
RevTurbine provider (AC-13).
## Parameters
### \_\_namedParameters
[`TelemetryScopeProps`](/docs/api/interfaces/telemetryscopeprops/)
## Returns
`ReactElement`
# Track
> **Track**(`__namedParameters`): `ReactElement`
Fire a telemetry event on click (plan 144 TASK-16 / REQ-22, AC-14).
With `asChild`, it **composes onto the single child element** rather than
wrapping it: the child's own `onClick` runs first, and the telemetry fires only
if the child did **not** `preventDefault`. Because it clones the child (no
wrapper element, no added `role`/`tabIndex`), the child's accessible name and
disabled state are unchanged (REQ-19). Without `asChild` it renders a host
element (default ``).
## Parameters
### \_\_namedParameters
[`TrackProps`](/docs/api/interfaces/trackprops/)
## Returns
`ReactElement`
# TrackOnView
> **TrackOnView**(`__namedParameters`): `ReactElement`
Emits `event` exactly once, the first time its host element is
viewport-qualified (plan 144 TASK-13 / REQ-21). Falls back to firing on render
when `IntersectionObserver` is unavailable (AC-10 degradation). The one-shot
guard is a ref that survives React Strict Mode's development double-mount, so
the event fires once, not twice (AC-12). Renders its children in the host
element and never throws without a provider (AC-13).
## Parameters
### \_\_namedParameters
[`TrackOnViewProps`](/docs/api/interfaces/trackonviewprops/)
## Returns
`ReactElement`
# unregisterCtaResolver
> **unregisterCtaResolver**(`type`): `boolean`
Remove a resolver from the default global registry.
Returns true if a resolver was registered and removed.
## Parameters
### type
`string`
## Returns
`boolean`
# useAddons
> **useAddons**(): `UseAddonsResult`
React access to the active user's public, segment-eligible add-on catalog.
## Returns
`UseAddonsResult`
# useCan
> **useCan**(`handle`, `options?`): [`UseCanResult`](/docs/api/interfaces/usecanresult/)
Reactive counterpart to the imperative `rt.can(handle)` — the entitlement check
as a `can` question. Takes the entitlement handle positionally (matching
`rt.can('handle')`), plus the same options [useEntitlement](/docs/api/functions/useentitlement/) accepts, and
returns a curated `{ can, limited, isLoading, result }`.
Deny-until-ready: `can` is `false` while the check is unresolved, flipping
within one microtask in local mode once the Playbook is in memory. Use
`isLoading` to distinguish "still deciding" from a settled deny.
For the full reactive surface (`allowed` / `denied` / `error` /
`gatedPlacement` / `recheck`), use [useEntitlement](/docs/api/functions/useentitlement/) directly.
## Parameters
### handle
`string`
### options?
`Omit`\<`UseEntitlementOptions`, `"handle"`\>
## Returns
[`UseCanResult`](/docs/api/interfaces/usecanresult/)
## Example
```tsx
function BatchExport() {
const { can, limited, isLoading } = useCan('batch_export');
if (!can && isLoading) return null; // still deciding — render nothing yet
if (!can) return ; // settled deny
return ;
}
```
# useEntitlement
> **useEntitlement**(`__namedParameters`): `UseEntitlementResult`
React hook that wraps `sdk.checkEntitlement()`.
Returns a reactive entitlement result that can drive access-gate UI.
Until the first check resolves, `allowed` and `denied` are both `false` and
`result` is `null` — the three-state model. Consumers decide from the triple
(`isLoading` / `allowed` / `denied`), never from `denied` alone; the SDK is
fail-closed, so "not yet allowed" is the default. Evaluation is local to the
loaded Playbook — there is no per-check network call.
## Parameters
### \_\_namedParameters
`UseEntitlementOptions`
## Returns
`UseEntitlementResult`
## Example
```tsx
function BrandKitSection() {
const { allowed, denied, result } = useEntitlement({ handle: 'brand_kit' });
if (denied) return ;
return ;
}
```
# useGatedAction
> **useGatedAction**\<`A`, `R`\>(`action`, `fn`, `context?`, `options?`): [`GatedAction`](/docs/api/interfaces/gatedaction/)\<`A`, `R`\>
The React analog of the headless `rt.gate(action, fn)` (plan 144 TASK-14 /
REQ-21, AC-11). `run` **delegates to `sdk.gate`**, so it emits the same active
gate sequence — `gate_attempted`, then `gate_allowed` or `gate_denied` — rather
than forking it. When allowed, the action runs wrapped in tracked-action
telemetry (`${action}_started` → `_completed` / `_failed`) and its return value
is preserved. Without a provider, `run` is a safe no-op that reports denied
(AC-13).
## Type Parameters
### A
`A` *extends* `unknown`[]
### R
`R`
## Parameters
### action
`string`
### fn
(...`args`) => `R` \| `Promise`\<`R`\>
### context?
`RevTurbineEntitlementContext`
### options?
[`TrackOptions`](/docs/api/interfaces/trackoptions/)
## Returns
[`GatedAction`](/docs/api/interfaces/gatedaction/)\<`A`, `R`\>
# usePlacement
> **usePlacement**(`__namedParameters`): [`UsePlacementResult`](/docs/api/type-aliases/useplacementresult/)
React hook for loading a placement decision and managing interactions.
Handles registration, decision fetching, impression tracking,
and exposes interaction methods (dismiss, remind me later, CTA click/complete).
## Parameters
### \_\_namedParameters
[`UsePlacementOptions`](/docs/api/type-aliases/useplacementoptions/)
## Returns
[`UsePlacementResult`](/docs/api/type-aliases/useplacementresult/)
## Example
```tsx
const { visible, content, ctaClick, dismiss } = usePlacement({
placement: { name: 'pricing_banner' },
userId: 'user_123',
});
if (!visible) return null;
return (
{content?.header}
);
```
# usePlacementPersonalization
> **usePlacementPersonalization**(`__namedParameters`): [`PersonalizationContext`](/docs/api/interfaces/personalizationcontext/)
Derive placement personalization tokens from the SDK's registered providers.
This hook resolves provider state through `sdk.providerRegistry.resolveAll()` so
token values honor provider strategies configured during SDK initialization.
## Parameters
### \_\_namedParameters
[`UsePlacementPersonalizationOptions`](/docs/api/interfaces/useplacementpersonalizationoptions/)
## Returns
[`PersonalizationContext`](/docs/api/interfaces/personalizationcontext/)
# usePlans
> **usePlans**(): `UsePlansResult`
React access to the active user's public, segment-eligible plan catalog.
## Returns
`UsePlansResult`
# useResolvedColorScheme
> **useResolvedColorScheme**(`preference`): `"light"` \| `"dark"`
Resolve a [RevTurbineColorScheme](/docs/api/type-aliases/revturbinecolorscheme/) to the concrete scheme to paint.
`'system'` tracks `prefers-color-scheme` and keeps tracking it: a user
flipping their OS theme updates the resolved value without a remount, which
is the difference between following the system and sampling it once at init.
## Parameters
### preference
[`RevTurbineColorScheme`](/docs/api/type-aliases/revturbinecolorscheme/)
The requested scheme. `'system'` follows the OS.
## Returns
`"light"` \| `"dark"`
`'light'` or `'dark'`.
# useRevTurbineTheme
> **useRevTurbineTheme**(): [`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)
Access the current SDK theme tokens from any component inside a
[RevTurbineProvider](/docs/api/functions/revturbineprovider/) tree.
## Returns
[`RevTurbineTheme`](/docs/api/interfaces/revturbinetheme/)
# useRevTurbineThemeProviderPresent
> **useRevTurbineThemeProviderPresent**(): `boolean`
Whether a [RevTurbineThemeProvider](/docs/api/functions/revturbinethemeprovider/) is already mounted above the caller.
Used by [RevTurbineProvider](/docs/api/functions/revturbineprovider/) to decide whether to mount its own. Exposed
so an app can make the same check — e.g. a design-system wrapper that only
supplies a theme when nothing upstream has.
## Returns
`boolean`
`true` when an ancestor mounted a theme provider.
# UserProfile
> **UserProfile**(`__namedParameters`): `any`
Read-only visualization of the resolved SDK user context.
Displays user id, plan, matched segments, usage balances, and traits that
were used for segment evaluation and placement decisioning.
## Parameters
### \_\_namedParameters
[`UserProfileProps`](/docs/api/interfaces/userprofileprops/)
## Returns
`any`
# useSlotRoute
> **useSlotRoute**(): `string` \| `null`
The route a slot rendered here would report: the nearest
[RevTurbineRoute](/docs/api/functions/revturbineroute/) pattern, else the browser's normalized pathname,
else `null` (server render). Read at call time.
## Returns
`string` \| `null`
# useSurfaceSlot
> **useSurfaceSlot**(`options`): `UseSurfaceSlotResult`
Hook that combines `usePlacement` decision loading with automatic
rendering via `PlacementRenderer`.
Returns everything `usePlacement` returns, plus an `element` property
containing a pre-rendered React element. Drop `element` into your JSX
to render the placement without writing custom rendering logic.
## Parameters
### options
`UseSurfaceSlotOptions`
## Returns
`UseSurfaceSlotResult`
## Example
```tsx
function FeatureGate() {
const { element, visible } = useSurfaceSlot({
surfaceSlot: { id: 'ai-export-gate', name: 'AI Export Gate' },
personalization: { user_name: 'Jane' },
onCtaClick: (uiPath) => handleCTA(uiPath),
});
return (
Export
{element}
);
}
```
# useTelemetryProps
> **useTelemetryProps**(`event`, `data?`, `options?`): `object`
Telemetry props to spread onto a primitive that **cannot take a wrapper** and
so can't use `