Skip to main content
Expys ships three official data SDKs. They are designed to feel like one library: the same method names, the same configuration options, and the same error taxonomy in every language. A concept you learn once - cursor pagination, automatic idempotency, token refresh, typed errors - applies unchanged across all three. Each SDK is fetch-only with zero runtime dependencies beyond its platform’s HTTP stack, holds a short-lived member token (never your Org-API-Key), and is currently in beta: the generated models and transport are stable to use while the ergonomic layer hardens. Pin an exact version in production and review the versioning policy.

TypeScript

@expys/sdk on npm. Works in browsers, Expo / React Native, and Node 18+.

Swift

ExpysSDK via SwiftPM or CocoaPods. iOS 15+, macOS 12+, async/await.

Kotlin

com.expys:sdk on Maven Central. Coroutine-native; one artifact for JVM and Android.

Install

npm install @expys/sdk
The first published version of every SDK is 0.1.0. CocoaPods (pod 'ExpysSDK', '~> 0.1') and Maven coordinates are covered on each language page.

One method surface, three languages

The member-mode flow - check eligibility, list offers, redeem the first one - is identical in shape across the SDKs. Here is the same flow in all three:
import { ConflictError, 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)",
  );
}

const expys = initialize({
  baseUrl: process.env.EXPYS_BASE_URL,
  environment: "sandbox",
  token,
});

async function main(): Promise<void> {
  const eligibility = await expys.eligibility();
  console.log(
    `tier: ${eligibility.tier}, balance: ${eligibility.wallet.balance}`,
  );

  const { data: offers } = await expys.listOffers({ limit: 10 });
  console.log(`browsed ${offers.length} offers`);

  const offer = offers[0];
  if (!offer) {
    return;
  }

  console.log(`redeeming: ${offer.title} (${offer.id})`);
  try {
    const redemption = await expys.createRedemption({ offer: offer.id });
    console.log(`redemption created: ${redemption.id} [${redemption.status}]`);

    const status = await expys.getRedemption(redemption.id);
    console.log(`status now: ${status.status}`);
  } catch (error) {
    if (error instanceof ConflictError) {
      console.log(`already redeemed: ${error.code}`);
      return;
    }
    throw error;
  }
}
The only intentional concurrency difference is the streaming return type (AsyncIterable / AsyncThrowingStream / Flow); everything else is guaranteed identical. See SDK differences for the full contract.

What’s shared

  • Member-mode and server-mode methods with the same names everywhere. Server-mode methods require an Org-API-Key and must run only on your backend.
  • A common configuration vocabulary: token, environment, baseUrl, refreshToken, tokenExpiresAt, maxRetries, timeout, and more. See the configuration reference.
  • One error taxonomy: every API error carries a stable code, an HTTP status, a coarse category, and a requestId. See Errors.
  • Built-in reliability: full-jitter retries on 429/5xx, automatic Idempotency-Key on writes, and proactive plus reactive token refresh.

Next steps

Quickstart

Install an SDK, mint a member token, and run your first flow.

Authentication

The two-token model and the token-refresh contract every SDK shares.

Configuration

Every option, with its default and per-language type.

SDK differences

The handful of intentional, idiomatic per-language differences.