Levered Docs
SDK

Vanilla JavaScript

Use the Levered SDK without React -- works in Node.js, Vue, Svelte, server-side handlers, and any JavaScript environment.

The vanilla integration exposes a LeveredClient class that works in any JavaScript or TypeScript environment -- browser, Node.js, edge runtimes, or server-side frameworks. Use it when you are not using React or need full control over variant fetching.

import { LeveredClient } from '@levered_dev/sdk';

LeveredClient

Create a client instance with your API URL and callbacks. The client handles retries, timeouts, and in-memory caching internally.

import { LeveredClient } from '@levered_dev/sdk';

const client = new LeveredClient({
  apiUrl: 'https://api.levered.dev',
  onExposure: (event) => logToWarehouse(event),
  onError: (err) => console.error('Levered error:', err),
  timeoutMs: 2000,
  maxRetries: 2,
  cacheTtlMs: 60000,
});

ClientConfig

OptionTypeDefaultDescription
apiUrlstring--Base URL of the Levered API (required).
onExposure(event: ExposureEvent) => void--Called when a variant is served to a user. Use this to log exposure events to your warehouse.
onError(error: unknown) => void--Called when a variant request fails after all retries. Useful for monitoring.
timeoutMsnumber2000Request timeout in milliseconds.
maxRetriesnumber2Number of retry attempts on network failure. Uses exponential backoff (200ms base).
cacheTtlMsnumber60000How long to cache variant responses in memory (milliseconds). Set to 0 to disable caching.

getVariant

Request the best variant for a specific optimization and user.

const result = await client.getVariant({
  anonymousId: 'user-123',
  optimizationId: 'abc-123',
  context: { country: 'US' },
});

if (result) {
  renderPage(result.variant);
} else {
  renderPage(fallbackVariant);
}

Options

OptionTypeRequiredDescription
anonymousIdstringYesStable anonymous identifier for the user or session.
optimizationIdstringYesUUID of the optimization to serve variants from.
contextRecord<string, unknown>NoContext attributes for contextual bandits (CMAB).
nVariantsnumberNoNumber of ranked variants to return (default: 1).

Return value

Returns VariantResult | null.

  • On success, returns an object with variant (the design-feature values) and optimizationId.
  • On any error (network failure, timeout, non-200 response, empty result), returns null.
interface VariantResult {
  variant: Record<string, string | number | boolean>;
  optimizationId: string;
  modelType: 'mab' | 'cmab' | 'holdout';
  ready: boolean;
}

You must handle the null case. Unlike the React hook, the vanilla client does not manage fallback values for you. Always provide your own default when the result is null.


ExposureEvent

When getVariant succeeds, the client fires your onExposure callback with this event:

interface ExposureEvent {
  anonymousId: string;
  optimizationId: string;
  variant: Record<string, string | number | boolean>;
  context: Record<string, unknown>;
  timestamp: string; // ISO-8601
  modelType: 'mab' | 'cmab' | 'holdout';
  ready: boolean;
}

Forward this event to your data warehouse. Levered reads exposure data from your warehouse during model training -- if exposures are not logged, the model cannot learn.


Server-side usage

The client works in server-side environments (Express, Fastify, Next.js API routes, etc.) since it uses the standard fetch API. Make sure your runtime supports fetch (Node.js 18+ has it built in, or use a polyfill).

Express example

import express from 'express';
import { LeveredClient } from '@levered_dev/sdk';

const app = express();
const levered = new LeveredClient({
  apiUrl: 'https://api.levered.dev',
  onExposure: (event) => {
    // Write to your exposure table or send to a queue
    insertExposure(event);
  },
});

const FALLBACK = { headline: 'Welcome', cta_text: 'Get Started' };

app.get('/', async (req, res) => {
  const anonymousId = req.cookies.session_id ?? generateId();

  const result = await levered.getVariant({
    anonymousId,
    optimizationId: 'abc-123',
    context: { country: req.headers['cf-ipcountry'] ?? 'US' },
  });

  const variant = result?.variant ?? FALLBACK;

  res.render('home', { headline: variant.headline, cta: variant.cta_text });
});

app.listen(3000);

Fastify example

import Fastify from 'fastify';
import { LeveredClient } from '@levered_dev/sdk';

const fastify = Fastify();
const levered = new LeveredClient({
  apiUrl: 'https://api.levered.dev',
  onExposure: (event) => insertExposure(event),
  timeoutMs: 1500,
});

const FALLBACK = { headline: 'Welcome', cta_text: 'Get Started' };

fastify.get('/', async (request, reply) => {
  const anonymousId = request.cookies.session_id ?? generateId();

  const result = await levered.getVariant({
    anonymousId,
    optimizationId: 'abc-123',
  });

  return {
    headline: result?.variant.headline ?? FALLBACK.headline,
    cta_text: result?.variant.cta_text ?? FALLBACK.cta_text,
  };
});

fastify.listen({ port: 3000 });

Caching behavior

The client caches variant responses in memory, keyed by the combination of optimizationId, anonymousId, and context. A cached response is returned immediately without a network request until it expires.

  • Default TTL is 60 seconds (cacheTtlMs: 60000).
  • Set cacheTtlMs: 0 to disable caching entirely.
  • The cache is per-client-instance and lives in memory. If you create the client at module scope on a server, the cache is shared across requests for the same process.

Error handling

The client never throws from getVariant. If the request fails, it retries up to maxRetries times with exponential backoff, then calls your onError callback (if provided) and returns null. Your application code only needs to check for null:

const result = await client.getVariant({ anonymousId, optimizationId });
const variant = result?.variant ?? fallback;