Amba

SDK Reference

The @amba/client TypeScript SDK and the @amba/expo Expo wrapper.

Packages

PackageDescription
@amba/clientCore TypeScript SDK (works in any JS runtime — Node, browsers, React Native, Expo).
@amba/expoExpo wrapper around @amba/client with AsyncStorage, push-token registration, Apple / Google sign-in helpers, and an Expo config plugin.

Installation

Two install paths — Path A is the reality right now; Path B lights up once the packages ship on npm.

Path A — monorepo + workspace:* (today)

@amba/cli, @amba/client, and @amba/expo are not yet on npm. Clone the monorepo and consume the SDKs via the pnpm workspace:* protocol — pnpm symlinks live source, so edits to packages/client/src/** reflect instantly in your app.

git clone https://github.com/AppMachina/amba.git
cd amba
pnpm install
pnpm -r run build
 
# Start a new Expo app inside examples/ so it joins the workspace:
cd examples
npx create-expo-app@latest my-app --template default
cd my-app
pnpm add @amba/expo@workspace:* @amba/client@workspace:* @amba/shared@workspace:*
pnpm add expo-notifications expo-device \
  expo-apple-authentication expo-auth-session \
  @react-native-async-storage/async-storage

examples/* is listed in pnpm-workspace.yaml, so pnpm picks up new apps automatically.

Path B — npm (coming soon)

# Not yet working — @amba/cli, @amba/client, @amba/expo are not on npm.
npx expo install @amba/expo expo-notifications expo-device \
  expo-apple-authentication expo-auth-session \
  @react-native-async-storage/async-storage
 
# Any other JS runtime
npm install @amba/client

@amba/expo declares expo-notifications, expo-device, expo-apple-authentication, expo-auth-session, and @react-native-async-storage/async-storage as peer dependencies.

@amba/shared intentionally keeps the referral-codes helper out of its barrel export because it imports node:crypto. Server-side code imports it explicitly via @amba/shared/referral-codes. React Native consumers never pay the node:crypto tax.

Configure the client

Core SDK — singleton

import { Amba } from '@amba/client';
 
// Configure once at app startup
Amba.configure({
  projectId: process.env.AMBA_PROJECT_ID!,
  apiKey: process.env.AMBA_API_KEY!,
  apiUrl: 'https://api.amba.dev', // optional, this is the default
  environment: 'production', // or 'development'
});
 
// Access anywhere
const client = Amba.client;
await client.init(); // restore session, ensure anonymous id, load config
await client.track('app_open');

Core SDK — instance

import { AmbaClient } from '@amba/client';
 
const client = new AmbaClient({
  projectId: 'proj_xxx',
  apiKey: 'amb_dev_ck_xxx',
});
 
await client.init();

Expo singleton

@amba/expo exports an Amba singleton that injects AsyncStorage and auto-registers the device push token for you:

// app/_layout.tsx
import { useEffect } from 'react';
import { Slot } from 'expo-router';
import { Amba } from '@amba/expo';
 
export default function RootLayout() {
  useEffect(() => {
    Amba.init({
      projectId: process.env.EXPO_PUBLIC_AMBA_PROJECT_ID!,
      apiKey: process.env.EXPO_PUBLIC_AMBA_API_KEY!,
      // Optional:
      // autoRegisterPushToken: false,
      // google: { clientId: 'YOUR_GOOGLE_OAUTH_CLIENT_ID' },
    });
  }, []);
 
  return <Slot />;
}

After Amba.init() completes you can use the passthrough accessors anywhere:

import { Amba } from '@amba/expo';
 
await Amba.track('lesson_completed', { lesson_id: '123' });
await Amba.signInWithApple(); // requires expo-apple-authentication
const streaks = await Amba.streaks.getAll();
const config = await Amba.configModule.getAll();

Note: the Expo wrapper exposes the config module as configModule (not config) to avoid clashing with React's config naming conventions.

Client modules

Every module below is available as client.<module> on AmbaClient and as Amba.<module> on the Expo wrapper. Method signatures are the real runtime shapes pulled from packages/client/src/*.ts.

client.authAuthModule

MethodSignature
getAnonymousId()Promise<string>
loginWithApple(identityToken)Promise<AuthResult>
loginWithGoogle(idToken)Promise<AuthResult>
signUpWithEmail(email, password)Promise<AuthResult>
loginWithEmail(email, password)Promise<AuthResult>
linkAccount(provider, token)Promise<AuthResult>provider is 'apple' | 'google'
getSession()Promise<Session | null>
refresh()Promise<Session> — rotate session + refresh tokens manually
me()Promise<AppUser> — fetch the current user record from the server
logout()Promise<void>
onAuthStateChange(cb)Unsubscribe

client.track(event, properties?) — events

Sends POST /client/events.

await client.track('workout_completed', { duration_minutes: 30 });

client.streaksStreakModule

MethodSignature
getAll()Promise<UserStreak[]>
qualify(streakId)Promise<UserStreak>

client.contentContentModule

MethodSignature
getToday()Promise<ContentItem[]>
getLibrary(libraryId, options?)Promise<ContentItem[]>
getItem(itemId)Promise<ContentItem>
createItem(libraryId, input)Promise<ContentItem> — server stamps ownership from the session
updateItem(itemId, input)Promise<ContentItem> — 404 if the item isn't owned by the current user
deleteItem(itemId)Promise<void> — 404 if the item isn't owned by the current user

client.configConfigModule

MethodSignature
get(key)Promise<unknown>
getAll()Promise<Record<string, unknown>>
refresh()Promise<void> — force-refresh from the server, bypassing the TTL
restore()Promise<void> — called automatically by init()

Backed by a TTL cache with ETag support. init() restores cached config from storage, then refreshes from the server in the background.

Internal: fetchConfig() is a private method. Use refresh() to force a server round-trip.

client.entitlements — EntitlementModule

MethodSignature
getAll()Promise<UserEntitlement[]>
isActive(entitlementId)Promise<boolean>

client.pushPushModule

MethodSignature
registerToken(token, platform)Promise<void>platform is 'ios' | 'android'
unregisterToken(token)Promise<void>

client.xp — XpModule

MethodSignature
getMyXp()Promise<UserXp>
getHistory(options?)Promise<XpLedgerEntry[]>
getRules()Promise<XpRule[]>

client.achievements — AchievementModule

MethodSignature
getAll()Promise<AchievementWithProgress[]>
get(achievementId)Promise<AchievementWithProgress>

client.leaderboards — LeaderboardModule

MethodSignature
getAll()List leaderboards
getEntries(leaderboardId, options?)Ranked entries
getMyRank(leaderboardId)Current user's rank

client.challenges — ChallengeModule

MethodSignature
getActive()Promise<ChallengeWithProgress[]>
join(challengeId)Join a challenge
getMine()User's challenges

client.currencies — CurrencyModule

MethodSignature
getBalances()Promise<CurrencyBalanceView[]>
getTransactions(options?)Promise<{ data: CurrencyTransaction[]; total: number }>

client.inventory — InventoryModule

MethodSignature
getAll()Promise<UserInventoryItem[]>
purchase(catalogItemId, currencyCode, storeId?)Promise<PurchaseResult>
consume(catalogItemId, quantity?)Promise<UserInventoryItem>

Other modules

The following modules are also exported and follow the same shape (see packages/client/src/ for full signatures): catalog, stores, deepLinks, feeds, friends, groups, media, messaging, moderation, onboarding, referrals, reviews, roles, sessions, sync.

Expo helpers

Exported by @amba/expo:

ExportDescription
AmbaSingleton wrapper. init(), passthrough module accessors, track(), signInWithApple(), signInWithGoogle(), registerPushToken().
asyncStorageAdapterAsyncStorage-backed AmbaStorage. Installed automatically by Amba.init() unless you pass your own storage.
registerForPushNotificationsAsync()Prompts for permission and returns { token, platform } or null (permission denied / simulator / unsupported).
signInWithApple() / isAppleAuthAvailable()expo-apple-authentication wrappers.
signInWithGoogle() / configureGoogleAuth(config)expo-auth-session wrappers.
@amba/expo/pluginExpo config plugin (push entitlements, URL schemes, associated domains, Android intent filters).

Custom storage

The SDK supports any async key/value storage. The default is in-memory (sessions are lost on restart):

import { AmbaClient, InMemoryStorage, type AmbaStorage } from '@amba/client';
 
const myStorage: AmbaStorage = {
  async getItem(key) {
    /* ... */ return null;
  },
  async setItem(key, value) {
    /* ... */
  },
  async removeItem(key) {
    /* ... */
  },
};
 
const client = new AmbaClient({
  projectId: 'proj_xxx',
  apiKey: 'amb_dev_ck_xxx',
  storage: myStorage,
});

In Expo, @amba/expo wires up asyncStorageAdapter for you.

Error handling

All HTTP failures throw AmbaApiError with status, code, and message:

import { AmbaApiError } from '@amba/client';
 
try {
  await client.auth.loginWithEmail('[email protected]', 'wrong');
} catch (err) {
  if (err instanceof AmbaApiError && err.code === 'INVALID_CREDENTIALS') {
    // show the user a friendly error
  } else {
    throw err;
  }
}

The HTTP client automatically retries 5xx responses up to 3 times with exponential back-off. 4xx responses are not retried.