# Runtime APIs — Bundle Drop Docs

> Reference for Bundle Drop runtime APIs: check, download and apply updates, discover channels, manage user properties, and read observability context.

Canonical: https://bundledrop.app/docs/runtime-apis

## What the Runtime APIs Are

The runtime APIs are the direct functions behind Bundle Drop's update behavior.

Use them when you want explicit control over checking, downloading, applying, channel discovery, bundle browsing, or user-property management.

Start by calling [BundleDrop.init](/docs/init-update-manager) once. After that, these APIs use the active runtime channel and app environment managed by the Bundle Drop runtime.

If you prefer React state and actions for manual UI flows, use [useBundleDrop](/docs/use-bundle-drop).

## Runtime Controller

### `BundleDrop.init({ environment, enabled?, channelName?, policy?, onStatusUpdate?, checkOnly? })`

Initializes Bundle Drop for the current app process.

- `environment` is required and should match the app environment you use for rollout targeting
- `enabled` defaults to `true`
- `channelName` sets the initial active channel
- `policy` controls startup behavior and defaults to `manual` when omitted
- `onStatusUpdate` receives progress messages
- `checkOnly` lets you initialize without automatically downloading or applying updates

Call this once near app startup before using the other runtime APIs.

### `BundleDrop.setChannel(channelName)`

Changes the active runtime channel for later checks, downloads, applies, and hook-driven actions.

### `BundleDrop.getChannelName()`

Returns the current active runtime channel outside React.

### `BundleDrop.reportHealthy()`

Asks native recovery to mark the exact OTA launch attempt captured by the current React runtime healthy. Returns `Promise<void>`.

Most apps do not need to call this because Bundle Drop uses automatic health reporting by default.

Use it when `rollback.healthCheckMode` is set to `"manual"` and your app wants to decide when startup is safe. In manual health mode, call it after your critical startup work completes. Matching duplicate calls are harmless and stale calls cannot commit a newer attempt. If you forget to call it, later distinct launches classify the unfinished attempt and Bundle Drop may eventually roll back the update.

## Core Update Functions

### `checkForUpdate(onStatusUpdate?)`

Checks whether the active channel has an update for the current app install:

The response includes the decision fields below:

```ts
Promise<
  | {
      action: "NOOP";
      upToDate?: boolean;
      channelName?: string;
      reason?: string;
      skippedFailedBundle?: boolean;
      skippedHash?: string;
      incompatible?: boolean;
      requestedRuntimeVersion?: string;
      latestRuntimeVersionOnChannel?: string;
    }
  | {
      action: "INSTALL";
      upToDate?: false;
      channelName?: string;
      hash?: string;
      bundleVersion?: number;
      version?: string;
      runtimeVersion?: string;
    }
  | {
      action: "ROLLBACK";
      channelName?: string;
      reason?: string;
    }
  | null
>
```

Use it when you want to check availability without downloading immediately.

`useBundleDrop().checkLatest()` wraps this for hook-based flows.

When `skippedFailedBundle` is `true`, Bundle Drop kept the current bundle because the selected update previously failed on this device. Treat that as no update for this install. Publish a new bundle if you need the device to try again.

Install decisions may also include delivery metadata that Bundle Drop uses to fetch the smallest safe download — a binary patch when one is available, or the full bundle otherwise. `downloadUpdate()` reads these fields for you, so treat them as diagnostic-only unless another API explicitly asks for them.

### `downloadUpdate(onStatusUpdate?)`

Downloads and stages an update:

```ts
Promise<
  | { status: "staged"; bundlePath: string; hash: string }
  | {
      status: "upToDate";
      reason?: string;
      skippedFailedBundle?: boolean;
      skippedHash?: string;
    }
  | { status: "disabled" }
  | { status: "incompatible" }
  | { status: "rollback"; reason?: string; pendingApply?: boolean }
>
```

Use it when you want to fetch an update but choose separately when to apply it. It uses the currently active runtime channel.

`useBundleDrop().downloadUpdate()` wraps this for hook-based flows.

When `status` is `"staged"`, the update is downloaded and ready to apply. When `skippedFailedBundle` is `true`, no download was attempted because that update previously failed on this device.

A `"rollback"` result with `pendingApply: true` means the rollback is staged rather than active. It follows the selected [update policy](/docs/update-policies); see [Rollback](/docs/rollback) for safety exceptions.

Bundle Drop automatically downloads the smallest safe update — a binary patch when one exists, falling back to the full bundle — and only reports `staged` after it has reconstructed and validated the complete bundle for local use. Your app does not need to know which delivery path was used.

### `applyUpdate(onStatusUpdate?)`

Applies a staged update:

```ts
Promise<
  | { status: "applied" }
  | { status: "noBundle" }
  | { status: "disabled" }
  | { status: "alreadyApplied" }
  | { status: "blocked"; reason: "ROLLBACK_REVALIDATION_FAILED" }
  | {
      status: "blocked";
      reason: "BUNDLE_PREVIOUSLY_FAILED";
      skippedHash?: string;
    }
>
```

Use it to explicitly apply a downloaded update or staged rollback.

`useBundleDrop().applyUpdate()` wraps this for hook-based flows.

When `applyUpdate()` returns `status: "blocked"`, inspect `reason`:

- `BUNDLE_PREVIOUSLY_FAILED`: keep the current bundle or publish a new update. Do not retry the bundle that failed on this device.
- `ROLLBACK_REVALIDATION_FAILED`: the staged rollback could not be confirmed against the current channel decision. Keep the running bundle and check for updates again before offering another apply action.

### `getUpdateState()`

Returns local update state:

```ts
Promise<{
  hasBundle: boolean;
  info: BundleInfo | null;
  pendingApply: boolean;
}>
```

Use this when your UI needs local update state. `pendingApply` can also be `true` for a staged rollback, including a return to the embedded bundle; it does not always mean a new bundle was downloaded.

### `getInstalledBundleInfo()`

Returns metadata for the installed OTA bundle:

```ts
Promise<BundleInfo | null>
```

Use it when you only need installed bundle metadata and do not need the full update state wrapper.

### `getObservabilityContext()`

Returns the current OTA context so you can wire crash and error reports to the exact bundle that is running:

```ts
Promise<{
  source: "embedded" | "ota";
  dist: string;
  tags: {
    bundle_drop_hash: string | null;
    bundle_drop_channel: string | null;
    bundle_drop_version: string | null;
    bundle_drop_runtime_version: string | null;
    bundle_drop_platform: string | null;
  };
  context: BundleInfo | null;
}>
```

Use `dist` as the release/dist identifier when uploading source maps to Sentry, Bugsnag, or Datadog, and attach `tags` to your reports so a stack trace maps back to the exact OTA bundle. Before an OTA bundle is running — including in development or when a staged update has not applied yet — `source` is `"embedded"` and `dist` is `"embedded"`. See [Observability](/docs/observability) for the full setup.

## Channel Discovery

### `getAvailableChannels()`

Returns public channel names for the configured project:

```ts
Promise<string[]>
```

`useBundleDrop().fetchAvailableChannels()` exposes the same capability in hook form and stores the result in `availableChannels`.

## Configuration

### `bundleDropConfig`

A read-only object that exposes the resolved project configuration for the current device. It is useful for dev tools, debug screens, or support dialogs without manually importing and parsing `bundle.drop.config.js`.

```ts
import { bundleDropConfig } from "@gfean/react-native-bundle-drop";
```

Shape:

```ts
{
  serverUrl: string;
  platform: "ios" | "android";
  runtimeVersion: string | undefined;
  defaultChannel: string;
  org: { slug: string };
  project: { name: string; slug: string };
  rollback: {
    maxCrashCount: number;
    healthCheckMode: "auto" | "manual";
    healthyAfterSec: number;
  };
}
```

Values are resolved at module load for the current device. The `platform` and `runtimeVersion` fields are already resolved, so you do not need to check `Platform.OS` yourself.

The project API key is intentionally excluded from this object.

The `BundleDropConfig` type is also exported for TypeScript consumers:

```ts
import type { BundleDropConfig } from "@gfean/react-native-bundle-drop";
```

## Bundle Browsing and Manual Install

These functions let users browse available bundles on a channel and install a specific version. This is useful for custom version pickers, QA tools, or rollback-style screens inside your app.

Bundle Drop handles the download and staging. Your app handles the UI.

### `getAvailableBundles(options?)`

Fetches a paginated list of downloadable bundles for a channel:

```ts
Promise<{
  items: BundleListItem[];
  nextCursor: string | null;
  hasMore: boolean;
}>
```

Each `BundleListItem` contains:

```ts
{
  hash: string;
  bundleVersion: number;
  version: string;
  platform: string;
  runtimeVersion: string;
  releaseNotes: string | null;
  createdAt: string;
  downloadUrl: string | null;
}
```

`hash` is the stable Bundle Drop identifier for that update. Use it for display, support, analytics, or error-reporting correlation. Most apps should not try to interpret it.

Options:

- `channelName`: defaults to the active runtime channel
- `platform`: defaults to the current device platform
- `limit`: number of items per page, from 1 to 50; default is 20
- `cursor`: pagination cursor from a previous response's `nextCursor`

Only downloadable bundles are returned. Results are sorted newest first.

`useBundleDrop().fetchBundles()` wraps this for hook-based flows.

### `installBundle(hash, downloadUrl, bundleVersion?, version?, runtimeVersion?, onStatusUpdate?)`

Downloads and stages a specific bundle by its hash and download URL. It returns the same shape as `downloadUpdate`:

```ts
Promise<
  | { status: "staged"; bundlePath: string; hash: string }
  | {
      status: "upToDate";
      reason?: string;
      skippedFailedBundle?: boolean;
      skippedHash?: string;
    }
  | { status: "disabled" }
  | { status: "incompatible" }
  | { status: "rollback"; reason?: string; pendingApply?: boolean }
>
```

Use the `downloadUrl` returned by `getAvailableBundles()`. After calling `installBundle`, call `applyUpdate()` to reload with the selected bundle.

`useBundleDrop().installBundle(bundleListItem)` wraps this for hook-based flows and accepts a `BundleListItem` directly.

### Typical usage

**version-picker.ts**

```ts
import {
  BundleDrop,
  getAvailableBundles,
  installBundle,
  applyUpdate,
} from "@gfean/react-native-bundle-drop";

BundleDrop.init({
  environment: "staging",
  channelName: "Beta",
  policy: "manual",
});

// Fetch first page of bundles
const page = await getAvailableBundles({
  limit: 10,
});

// Fetch next page
await getAvailableBundles({
  cursor: page.nextCursor,
});

// User picks a bundle
const selected = page.items[3];
if (!selected?.downloadUrl) {
  throw new Error("Selected bundle is not downloadable");
}

await installBundle(
  selected.hash,
  selected.downloadUrl,
  selected.bundleVersion,
  selected.version,
  selected.runtimeVersion,
);

// Apply the selected version
await applyUpdate();
```

## User Property Functions

These functions manage key-value user properties on the device. They are used for [Targeted Rollout](/docs/targeted-rollout-properties) and audience estimation.

Property keys and string values are **case-sensitive**. `"tier"` and `"Tier"` are treated as different keys.

Values can be strings, finite numbers, or booleans. Use the same type you selected for the property in the dashboard.

Keys are trimmed before they are saved. They must be non-empty, 128 characters or fewer, and cannot start with `$`, contain `.` or a null byte, or use reserved prototype names.

### `setUserProperty(key, value)`

Sets a key-value property for the current app install:

```ts
Promise<Record<string, string | number | boolean>>
```

The returned object is the full set of user properties after the change. If the key already has the same value, the call is a no-op.

### `removeUserProperty(key)`

Removes a property by key:

```ts
Promise<Record<string, string | number | boolean>>
```

The returned object is the full set after removal.

### `getUserProperties()` / `getCurrentUserProperties()`

Each returns:

```ts
Promise<Record<string, string | number | boolean>>
```

Use these when you need to inspect the currently stored properties.

### `resetUserProperties()`

Clears all user properties for the current app install. Returns `Promise<void>`.

## Hook vs Direct Functions

A practical way to choose:

- use [useBundleDrop](/docs/use-bundle-drop) when building manual React UI
- use direct runtime functions when you want lower-level control
- use [BundleDrop.init](/docs/init-update-manager) when you want automated startup handling or shared runtime state

Some direct runtime functions are also surfaced through the hook:

- `checkForUpdate` -> `useBundleDrop().checkLatest()`
- `downloadUpdate` -> `useBundleDrop().downloadUpdate()`
- `applyUpdate` -> `useBundleDrop().applyUpdate()`
- `getAvailableChannels` -> `useBundleDrop().fetchAvailableChannels()`
- `getAvailableBundles` -> `useBundleDrop().fetchBundles()`
- `installBundle` -> `useBundleDrop().installBundle()`

## Related Docs

- For runtime startup setup, see [BundleDrop.init](/docs/init-update-manager).
- For manual UI-driven flows, see [useBundleDrop](/docs/use-bundle-drop).
- For policy behavior, see [Update Policies](/docs/update-policies).
- For audience targeting, see [Targeted Rollout](/docs/targeted-rollout-properties).
- For wiring OTA context into error tracking, see [Observability](/docs/observability).
