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
Section titled “Example”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
Section titled “Methods”can(
handle,context?):Promise<{ }>
Check whether the user can do something — the advertised alias of
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
Section titled “Parameters”handle
Section titled “handle”string
context?
Section titled “context?”RevTurbineEntitlementContext
Returns
Section titled “Returns”Promise<{ }>
Example
Section titled “Example”const access = await rt.can('generate_image');if (!access.allowed) showUpgrade();
@publiccheckEntitlement()
Section titled “checkEntitlement()”checkEntitlement(
handle,context?):Promise<{ }>
Evaluate a single entitlement handle for the active user, purely from the loaded UserContext and Playbook. The advertised 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.
Parameters
Section titled “Parameters”handle
Section titled “handle”string
context?
Section titled “context?”RevTurbineEntitlementContext
Returns
Section titled “Returns”Promise<{ }>
checkoutMetadata()
Section titled “checkoutMetadata()”checkoutMetadata(
outputId):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
Section titled “Parameters”outputId
Section titled “outputId”string
Returns
Section titled “Returns”RevTurbineCheckoutMetadata | null
null when no decision from this SDK produced outputId.
convert()
Section titled “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:
- The interaction is recorded (analytics + attribution), as before.
- 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, soGET /api/sdk/client-contextcan 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. - 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.
- 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 PlacementControllers re-decide and mounted EntitlementGates
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
Section titled “Parameters”outputId
Section titled “outputId”string
Returns
Section titled “Returns”Promise<void>
diagnoseSlotInventory()
Section titled “diagnoseSlotInventory()”diagnoseSlotInventory():
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
Section titled “Returns”RevTurbineSlotInventoryDiagnosis
dismiss()
Section titled “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
Section titled “Parameters”outputId
Section titled “outputId”string
Returns
Section titled “Returns”Promise<void>
dispose()
Section titled “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
Section titled “Returns”void
emitPlatformEvent()
Section titled “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: 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
Section titled “Type Parameters”K extends EmittablePlatformEventName
Parameters
Section titled “Parameters”eventName
Section titled “eventName”K
payload
Section titled “payload”EventPayloadInput<K>
options?
Section titled “options?”RevTurbineEventOptions
Returns
Section titled “Returns”Promise<void>
emitSemantic()
Section titled “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, 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.<field> 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
Section titled “Parameters”eventType
Section titled “eventType”string
payload
Section titled “payload”options?
Section titled “options?”RevTurbineEventOptions
Returns
Section titled “Returns”Promise<void>
emitTrigger()
Section titled “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 that constrains the event name to 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
Section titled “Parameters”trigger
Section titled “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?
Section titled “payload?”options?
Section titled “options?”RevTurbineEventOptions
Returns
Section titled “Returns”Promise<void>
Example
Section titled “Example”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 aboveawait sdk.emitTrigger('feature_gated', { feature: 'advanced_automation' });explainPlacementDecision()
Section titled “explainPlacementDecision()”explainPlacementDecision(
input):Promise<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
Section titled “Parameters”RevTurbinePlacementDecisionInput
Returns
Section titled “Returns”Promise<RevTurbinePlacementDecisionExplanation>
fetchClientContext()
Section titled “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
Section titled “Parameters”clientToken?
Section titled “clientToken?”string
the rt_client_ token; when omitted, reuses the last one.
Returns
Section titled “Returns”Promise<void>
fetchUserContext()
Section titled “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
Section titled “Parameters”userId
Section titled “userId”string
Returns
Section titled “Returns”Promise<UserTargetingContext>
flushEvents()
Section titled “flushEvents()”flushEvents():
Promise<void>
Flush any buffered clickstream events to POST /api/track immediately.
Called on the size threshold, the 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
Section titled “Returns”Promise<void>
gate()
Section titled “gate()”gate<
T>(action,fn,context?):Promise<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 <RTSlot>). See RevTurbineGateResult.
Type Parameters
Section titled “Type Parameters”T
Parameters
Section titled “Parameters”action
Section titled “action”string
() => T | Promise<T>
context?
Section titled “context?”RevTurbineEntitlementContext
Returns
Section titled “Returns”Promise<RevTurbineGateResult<T>>
Example
Section titled “Example”const gated = await rt.gate('export_pdf', () => exportPdf());if (!gated.ran) openPaywall(gated.entitlement);
@publicgetBranding()
Section titled “getBranding()”getBranding():
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.
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
Section titled “Returns”The merged branding and the ladder rung it came from.
getEffectiveUserContext()
Section titled “getEffectiveUserContext()”getEffectiveUserContext():
Promise<Readonly<RevTurbineUserContext>>
Return the immutable ephemeral context currently entering local evaluation.
Returns
Section titled “Returns”Promise<Readonly<RevTurbineUserContext>>
getEligibleAddons()
Section titled “getEligibleAddons()”getEligibleAddons():
Promise<EligibleAddon[]>
Return the public add-on variations eligible for the active user.
Returns
Section titled “Returns”Promise<EligibleAddon[]>
getEligiblePlans()
Section titled “getEligiblePlans()”getEligiblePlans():
Promise<EligiblePlan[]>
Return the public plan variations eligible for the active user.
Returns
Section titled “Returns”Promise<EligiblePlan[]>
getEntitlements()
Section titled “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
Section titled “Returns”Record<string, EntitlementResult>
getExperimentVariant()
Section titled “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
Section titled “Parameters”experimentHandle
Section titled “experimentHandle”string
Returns
Section titled “Returns”Promise<ExperimentVariantSelection>
getExportedConfig()
Section titled “getExportedConfig()”getExportedConfig(): { } |
undefined
Returns
Section titled “Returns”{ } | undefined
getPersonalizationTokens()
Section titled “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
Section titled “Parameters”payload?
Section titled “payload?”Optional placement payload carrying the per-placement recommendation strategy. Omit for the user-level default.
recommendation_plan_override?
Section titled “recommendation_plan_override?”string | null
recommendation_strategy?
Section titled “recommendation_strategy?”RecommendationStrategy | null
Returns
Section titled “Returns”RevTurbinePersonalizationTokens
getPlacement()
Section titled “getPlacement()”getPlacement(
config):Promise<{ } |null>
Resolve the legacy placement payload for a request config. Prefer getPlacementDecision for new integrations; kept for the documented legacy request-config path.
Parameters
Section titled “Parameters”config
Section titled “config”RevTurbinePlacementRequestConfig
Returns
Section titled “Returns”Promise<{ } | null>
getPlacementDecision()
Section titled “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, dismiss, snooze, convert).
Parameters
Section titled “Parameters”RevTurbinePlacementDecisionInput
Returns
Section titled “Returns”Promise<RevTurbinePlacementDecision>
getPlaybook()
Section titled “getPlaybook()”getPlaybook(): { } |
undefined
Returns the Playbook snapshot loaded at initialization.
Canonical consumers should retain their own Playbook or use normalizeConfigArtifactOrThrow at their ingestion boundary rather than reading it back out of the SDK.
Returns
Section titled “Returns”{ } | undefined
getPolicy()
Section titled “getPolicy()”getPolicy():
RevTurbinePolicySnapshot
Return current SDK policy snapshot.
Returns
Section titled “Returns”getRegisteredSlots()
Section titled “getRegisteredSlots()”getRegisteredSlots():
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
Section titled “Returns”getTargeting()
Section titled “getTargeting()”getTargeting():
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
Section titled “Returns”getTelemetryConsent()
Section titled “getTelemetryConsent()”getTelemetryConsent():
TelemetryConsent
The current behavior-telemetry consent state (plan 144 TASK-8).
Returns
Section titled “Returns”getTelemetryCounters()
Section titled “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
Section titled “Returns”TelemetryCounters
getTrialStatus()
Section titled “getTrialStatus()”getTrialStatus():
Promise<{ }>
Return the active user’s trial status, derived from their trial instances and the configured reverse-trial rules.
Returns
Section titled “Returns”Promise<{ }>
getUsage()
Section titled “getUsage()”getUsage():
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
Section titled “Returns”getUserContext()
Section titled “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
Section titled “Returns”object
hydrate()
Section titled “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
Section titled “Parameters”payload
Section titled “payload”Returns
Section titled “Returns”void
Example
Section titled “Example”// In your React component / page hydration:const session = await initRevTurbine({ tenantId, apiKey, endpoint, mode: 'react' });session.sdk.hydrate(serverPayload);identify()
Section titled “identify()”identify<
T>(userId,context?):void
Identify the current user and (optionally) supply their context.
Accepts the canonical 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:<user_id> instead — the acting user’s id
behind the 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
Section titled “Type Parameters”T extends IdentifyContextInput
Parameters
Section titled “Parameters”userId
Section titled “userId”string
context?
Section titled “context?”Exact<IdentifyContextInput, T>
Returns
Section titled “Returns”void
Example
Section titled “Example”rt.identify('user_123', { plan_handle: 'pro' }); // THE matching identityrt.identify('user_123', { plan_handle: 'pro', plan: { handle: 'pro', name: 'Professional' } });onUserContextChange()
Section titled “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
Section titled “Parameters”listener
Section titled “listener”() => void
Returns
Section titled “Returns”() => void
Example
Section titled “Example”const unsubscribe = rt.onUserContextChange(() => refreshMyUi());persistPlacementTypes()
Section titled “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
Section titled “Parameters”RevTurbinePlacementTypeEntity[]
Returns
Section titled “Returns”Promise<void>
recordAccountCreated()
Section titled “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
Section titled “Parameters”payload
Section titled “payload”AccountCreatedPayload
Returns
Section titled “Returns”Promise<{ reason: string | null; recorded: boolean; }>
recordClickThru()
Section titled “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.
Parameters
Section titled “Parameters”placementId
Section titled “placementId”string
The placement’s stable rule_id.
payloadId?
Section titled “payloadId?”string
Optional payload variant id.
surfaceTemplateId?
Section titled “surfaceTemplateId?”string
Optional surface template id.
metadata?
Section titled “metadata?”Optional metadata persisted with the record.
cooldownMs?
Section titled “cooldownMs?”number
Optional explicit cooldown window (ms). Defaults to 7 days.
Returns
Section titled “Returns”Promise<void>
recordConversion()
Section titled “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
Section titled “Parameters”placementId
Section titled “placementId”string
The placement’s stable rule_id.
payloadId?
Section titled “payloadId?”string
Optional payload variant id.
surfaceTemplateId?
Section titled “surfaceTemplateId?”string
Optional surface template id.
metadata?
Section titled “metadata?”Optional metadata persisted with the record.
Returns
Section titled “Returns”Promise<void>
recordDismissal()
Section titled “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.
Parameters
Section titled “Parameters”placementId
Section titled “placementId”string
The placement’s stable rule_id.
payloadId?
Section titled “payloadId?”string
Optional payload variant id.
surfaceTemplateId?
Section titled “surfaceTemplateId?”string
Optional surface template id.
metadata?
Section titled “metadata?”Optional metadata persisted with the record.
cooldownMs?
Section titled “cooldownMs?”number
Optional explicit cooldown window (ms). Defaults to 7 days.
Returns
Section titled “Returns”Promise<void>
recordImpression()
Section titled “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 inapplyContentMilestoneSupersession.
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
Section titled “Parameters”placementId
Section titled “placementId”string
The placement’s stable rule_id (e.g.
'pl_trial_progress_70'). Match decision.output.rule_id
from getPlacementDecision.
payloadId?
Section titled “payloadId?”string
Optional payload variant id, for variant-level analytics.
surfaceTemplateId?
Section titled “surfaceTemplateId?”string
Optional surface template id, for per-surface cap accounting.
metadata?
Section titled “metadata?”Optional metadata persisted with the record.
Returns
Section titled “Returns”Promise<void>
Example
Section titled “Example”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()
Section titled “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
Section titled “Parameters”episode
Section titled “episode”TrialEpisodeFacts
The episode’s facts, as your app holds them.
labels?
Section titled “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
Section titled “Returns”Promise<TrialRevisionRecordResult>
registerPlacement()
Section titled “registerPlacement()”registerPlacement(
config):Promise<string>
Parameters
Section titled “Parameters”config
Section titled “config”Returns
Section titled “Returns”Promise<string>
registerSurfaceSlot()
Section titled “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
Section titled “Parameters”config
Section titled “config”Returns
Section titled “Returns”Promise<string>
reportSdkError()
Section titled “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
Section titled “Parameters”reason
Section titled “reason”string
Stable, categorical cause (e.g. provider_init_failed) —
what dashboards group by.
message?
Section titled “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
Section titled “Returns”void
reset()
Section titled “reset()”reset():
void
Clear the current user — the advertised alias of resetIdentity (e.g. on sign-out).
Returns
Section titled “Returns”void
resetIdentity()
Section titled “resetIdentity()”resetIdentity():
void
Reset to anonymous user state, re-inferring a fresh anonymous id. Also available as the advertised reset alias.
Returns
Section titled “Returns”void
resetUserContext()
Section titled “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 (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
Section titled “Returns”void
Example
Section titled “Example”// Between demo personas:rt.resetUserContext();rt.identify('demo_pro', { plan_handle: 'pro' });setApiBranding()
Section titled “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
Section titled “Parameters”branding
Section titled “branding”{ } | undefined
Branding from the Branding API. Overrides config-embedded
branding but never the explicit branding init option.
Returns
Section titled “Returns”void
setTelemetryConsent()
Section titled “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
Section titled “Parameters”consent
Section titled “consent”the new consent state
Returns
Section titled “Returns”void
setTrialInstances()
Section titled “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
Section titled “Parameters”instances
Section titled “instances”readonly object[]
The user’s trial instance records.
options?
Section titled “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?
Section titled “basePlanHandle?”string
nowIso?
Section titled “nowIso?”string
Returns
Section titled “Returns”Promise<{ }>
setUserContext()
Section titled “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 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 and update do; that
Exact<> narrowing is deferred to 0.12.0 because it can break callers
that pass a wider object today.
Parameters
Section titled “Parameters”userContext
Section titled “userContext”Returns
Section titled “Returns”void
snooze()
Section titled “snooze()”snooze(
outputId,seconds?):Promise<void>
Suppress a decision’s placement for a “remind me later” window —
seconds overrides the authored default.
Parameters
Section titled “Parameters”outputId
Section titled “outputId”string
seconds?
Section titled “seconds?”number
Returns
Section titled “Returns”Promise<void>
syncTrialRevisions()
Section titled “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 directly if you would
rather push episodes than have them pulled.
Parameters
Section titled “Parameters”labels?
Section titled “labels?”TrialRevisionLabels = {}
Returns
Section titled “Returns”Promise<TrialRevisionRecordResult[]>
track()
Section titled “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 /
setUserContext). account_id carries the identified account when
there is one and the labelled fallback user-fallback:<user_id> when there
is not — never a bare, unlabelled user id (BL-0117, Kent’s D-13).
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Promise<void>
Example
Section titled “Example”rt.track('ai_generation_completed', { credits: 3 });
@publictrackControlPlaneEvent()
Section titled “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).
Identity comes from the active user context set via identify /
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:<user_id> — a bare, unlabelled user id is never stamped
into an account column (BL-0117, Kent’s D-13). Classify it with
isFallbackAccountId.
Parameters
Section titled “Parameters”eventType
Section titled “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?
Section titled “payload?”SdkEventProperties = {}
Optional event-specific properties (e.g. { resource, resource_id }).
options?
Section titled “options?”RevTurbineEventOptions
Emit options, e.g. { immediate: true } to bypass batching.
Returns
Section titled “Returns”Promise<void>
Example
Section titled “Example”sdk.identify('operator_42', { account_id: 'tn_acme' });await sdk.trackControlPlaneEvent('playbook_version_deployed', { change_set_id: 'cs_9' });trackTreatmentInteraction()
Section titled “trackTreatmentInteraction()”trackTreatmentInteraction(
input):Promise<void>
Report a treatment interaction directly by placement/treatment id — the
lower-level counterpart to dismiss/snooze/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) also emits
checkout_started — never for a CTA that opens checkout directly.
Parameters
Section titled “Parameters”RevTurbineTreatmentInteractionInput
Returns
Section titled “Returns”Promise<void>
update()
Section titled “update()”update<
T>(patch):void
Patch the session-bound user context — the advertised update(patch) verb.
Accepts every session-scoped RevTurbineUserContext field except the
identity handle id (see RevTurbineUpdateInput); to (re)establish
identity use identify.
usage routes through updateUsage (absolute balances + usage
threshold-crossing events); every other recognized field routes through
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({ <handle>: n }) is not a usage report.
Type Parameters
Section titled “Type Parameters”T extends RevTurbineUpdateInput
Parameters
Section titled “Parameters”Exact<RevTurbineUpdateInput, T>
Returns
Section titled “Returns”void
Examples
Section titled “Examples”// Bump reported usage:rt.update({ usage: { generations: 25 } });// Reflect a plan change and new traits in one call (identity unchanged):rt.update({ plan_handle: 'pro', custom: { role: 'admin' } });
@publicupdateUsage()
Section titled “updateUsage()”updateUsage(
balances):void
Set absolute usage balances for the active user and emit any
threshold-crossing triggers. See the advertised update alias’s
usage key for the same contract via the general patch verb.
Parameters
Section titled “Parameters”balances
Section titled “balances”Returns
Section titled “Returns”void
validateUiPathResolvers()
Section titled “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:
uiPathResolverspassed at SDK init- optional
resolverspassed to this method - CTA handlers from domain providers (
domain: 'cta'), unless disabled
Parameters
Section titled “Parameters”options?
Section titled “options?”RevTurbineUiPathResolverValidationOptions = {}
Returns
Section titled “Returns”Promise<RevTurbineUiPathResolverValidationReport>