Skip to main content

Overview

Config Bundles are versioned JSON snapshots of an app’s published configuration: onboarding flows, paywalls, in-app messages, surveys, experiments, feature flags and remote config. You download one at build time and ship it in your app as appdna-config.json, the SDK’s offline fallback. The SDK itself never calls these endpoints: it reads live config from Firestore after its bootstrap request (see Offline-First Architecture).

Endpoints

Download Latest Bundle

Authentication: SDK Key (x-api-key header) Returns the most recently generated bundle, including all content. Use this in your build to embed the bundle. When no bundle has been generated, data is null (HTTP 200, with a message). Response: The bundle is wrapped in a top-level data object.

Check Bundle Version

Authentication: SDK Key (x-api-key header) Lightweight version check that returns metadata without the full content payload — for example, to decide in CI whether to download a new bundle. When no bundle exists, bundle_version is 0. Response: Metadata is wrapped in a top-level data object.
This endpoint returns only metadata (no content field), making it ideal for frequent polling without unnecessary bandwidth usage.

Force Regenerate Bundle

Authentication: Customer JWT (Authorization: Bearer header) of an organization admin or owner — a member or viewer gets 403. Forces the server to regenerate the config bundle. Returns the newly generated bundle, always as a new version. Publishing regenerates the bundle on its own (see Generation); use this when a build must not wait for that. Response: HTTP 201 Created. Same shape as the Download endpoint — the bundle wrapped in a top-level data object.

Bundle Version History

Authentication: Customer JWT (Authorization: Bearer header) Returns a paginated list of previous bundle versions. Query parameters:

SDK Bootstrap

Authentication: SDK Key (x-api-key header) Returns the org/app context and runtime settings the SDK needs to initialize. The SDK calls it at configure, and again only if that attempt failed; it does not call it when the config TTL elapses. It is rate-limited per device, to 300 requests a minute for each key and caller IP address (an IPv6 address counts by its /64), and across all devices of a key to 3,000 requests a minute (10,000 on Pro, 30,000 on Autopilot, 100,000 on Enterprise); a refused request gets 429 with a Retry-After header. The response is never cached (Cache-Control: private, no-store). Response:
From iOS 1.0.82 and Android 1.0.54 — and the Flutter and React Native releases whose appdna_feature_parity is 1.0.82 — the SDKs apply the three settings when a bootstrap succeeds, with this precedence for each: the option the app passed to configure, then the bootstrap value, then the SDK’s built-in default (flushInterval 30 s, no batchSize cap, configTTL 3600 s). Until a bootstrap succeeds, or if the answer lacks a value or holds 0 or less, the built-in default stays. AppDNA sets these values for all apps; they equal the built-in defaults today. Older SDKs ignore them.
For a tenant whose billing has lapsed or whose account has been locked, the response also carries a runtime_lock object; the SDK reads it and enters a safe read-only mode automatically — your app code does not need to handle it.

Bundle Content Schema

The content field holds the app’s published config, one key per type, read from the same Firestore documents the SDK reads live: for paywalls, onboarding flows, surveys, messages and flags, each item’s own document as listed by its index. Each value has the shape the SDK parses: paywalls is { "paywalls": { <id>: <paywall> } }, onboarding is { "active_flow_id": …, "flows": { <id>: <flow> } }, surveys is { "surveys": { … } }, messages is { "messages": { … } }, and remote_config / feature_flags map each flag key to its value (a flag with no value is left out). There is no size limit: a bundle may be larger than 1 MB.

Content Types


How the SDK Uses an Embedded Bundle

The native SDK (iOS, Android, and so Flutter and React Native) reads appdna-config.json from the app bundle (iOS) or assets/ (Android). It reads the file once its bootstrap request has succeeded or failed. The file must hold the content keys above at its top level, plus an optional bundle_version. The SDK uses it only for sections that have no cached config, except screen_index and, on iOS, brand, which it applies from the file whatever the cache holds (Android does not read brand from the file); a remote fetch replaces it. Image URLs in the file are loaded from the network like any other, with one exception: the Console’s Bundle + Assets (ZIP) download rewrites them to appdna-assets/<file> paths, and only the iOS SDK resolves those (from the app bundle); the Android SDK, and so the Android build of a Flutter or React Native app, does not load them (see Bundle + Assets (ZIP)). Once it has loaded a file whose bundle_version is greater than 0, the SDK sends that bundle_version in the device context of every event; with no file, or a file without one, events carry none.

Generation

The server regenerates the bundle shortly after every publish that changes what the SDK reads — paywalls, onboarding flows and their option sets, in-app messages, surveys, experiments, feature flags and remote config, server-driven screens and the brand palette. Publishes within the same fixed 15-second clock window (:00–:15, :15–:30, and so on) are built together, once, about a second after the window ends, so a new bundle is usually ready within about 20 seconds of a publish; a failed build is retried and never fails the publish. A rebuild that would produce the same content as the current bundle creates no new version. generated_by is system:publish for these. POST /config-bundle/generate and Regenerate under Settings → SDK → Config Bundle in the Console generate one on request, always as a new version.

CI/CD Integration

So that a first launch without network has config, download the config bundle at build time and embed it as a fallback:
The embedded bundle is a fallback only. The SDK fetches live config from Firestore at configure and replaces the bundled sections when that fetch completes. Download the bundle in every release build, after your last publish has been regenerated, so it does not become stale.
Your config bundle integration is working when the SDK logs Loaded bundled config (version N) during initialization (info level) and, with the device offline on a fresh install, your published onboarding flows or paywalls render.