Skip to main content
The official Expys data SDK for TypeScript. Embed Expys experiences into your web, React Native, or Node app. It is fetch-only with zero runtime dependencies, and ships its own types.
Beta. The generated types and transport are stable to use; the ergonomic layer is hardening during the rollout window. Pin an exact version in production and review the versioning policy.

Install

npm install @expys/sdk

Initialize

The SDK is constructed with initialize({ token, environment, ... }). The token is a short-lived member token your backend obtained from POST /v1/auth/exchange - never your Org-API-Key.
import { initialize } from "@expys/sdk";

const expys = initialize({
  token: memberToken,
  environment: "live", // or "sandbox"
});

const { data: offers } = await expys.listOffers({ limit: 20 });
Provide a refreshToken hook and the SDK refreshes the member token automatically near expiry and once on a 401. The hook must call your backend (which re-exchanges the Org-API-Key) and return { accessToken, expiresAt? }. See Authentication for the full contract.

Configuration

initialize accepts the shared configuration vocabulary - baseUrl, maxRetries, timeoutMs, a custom fetch, and more. Here it is alongside the Swift and Kotlin equivalents:
import { initialize } from "@expys/sdk";

const token = process.env.EXPYS_MEMBER_TOKEN;
if (!token) {
  throw new Error(
    "Set EXPYS_MEMBER_TOKEN (a member token from your backend's /v1/auth/exchange)",
  );
}

// A custom fetch wrapper: log each request, then delegate to the platform fetch.
// Use this seam for tracing, metrics, or a polyfill on Node < 18. The cast keeps
// it simple here under bun-types (whose `fetch` carries extra members); in a
// typical web/Node project the arrow satisfies `typeof fetch` without a cast.
const instrumentedFetch = ((input, init) => {
  const method = init?.method ?? "GET";
  const url =
    typeof input === "string"
      ? input
      : input instanceof URL
        ? input.href
        : input.url;
  console.log(`-> ${method} ${url}`);
  return fetch(input, init);
}) as typeof fetch;

const expys = initialize({
  baseUrl: process.env.EXPYS_BASE_URL,
  environment: "sandbox",
  fetch: instrumentedFetch,
  // Retry 429/5xx up to 3 extra times (4 attempts total) with backoff.
  maxRetries: 3,
  // Abort any single attempt that exceeds 8s.
  timeoutMs: 8_000,
  token,
});

async function main(): Promise<void> {
  const { data } = await expys.listOffers({ limit: 3 });
  console.log(`fetched ${data.length} offers with the configured client`);
}
The full option table, with defaults and per-language types, lives in the configuration reference.

Errors

Every failed request throws a typed error carrying the stable envelope code. Branch on the class, then refine with the code:
import { ConflictError, RateLimitError } from "@expys/sdk";

try {
  await expys.createRedemption({ offer });
} catch (error) {
  if (error instanceof ConflictError && error.code === "REDEMPTION_ALREADY_EXISTS") {
    // the member already booked this offer
  }
  if (error instanceof RateLimitError) {
    // error.retryAfterMs is set
  }
}
The classes are ApiError (base for HTTP errors) and UnauthorizedError, ForbiddenError, NotFoundError, ConflictError, ValidationError, RateLimitError, ServerError, plus NetworkError / TimeoutError and the common base ExpysError. Treat an unknown code as the generic class for its status - new codes can appear in a minor release. See Errors.

Runtime support

Works anywhere a standard fetch exists: modern browsers (Vite / web), Expo / React Native, and Node 18+. Provide a fetch polyfill for older runtimes; the built package is smoke-tested on Node 18 / 20 / 22, Bun, and Deno. The SDK sends no telemetry.

Next steps

Authentication

Minting member tokens and the refresh hook.

Configuration

Every option with its default.

Errors

The error taxonomy and stable codes.

API reference

Every endpoint, with a live “Try it” playground.