Config Bundles in CI/CD
A config bundle is a versioned JSON snapshot that contains everything the SDK needs to run without a network connection: onboarding flows, paywalls, in-app messages, feature flags, experiment definitions, and remote config values. By embedding a config bundle in your app binary during CI/CD, a first launch without an internet connection can still show the paywalls, onboarding flows, messages, surveys, flags and experiments the bundle held when you downloaded it. It is a fallback, not a guarantee: the SDK uses the file only for sections it has no cached config for (with the exceptions in How the SDK Uses an Embedded Bundle), the file is only as current as the bundle your build downloaded, and a successful remote fetch replaces it.What is a Config Bundle?
The config bundle is a JSON snapshot generated server-side from your app’s published config, read from the same documents the SDK reads live, so it holds every published item however large your paywalls or flows are. The server regenerates it 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 15-second window are built together, once, 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. You can also generate one at any time with Regenerate under Settings → SDK → Config Bundle in the Console, or withPOST /api/v1/sdk/config-bundle/generate. Its content holds:
Each bundle includes a
bundle_version number that increments with every new bundle, along with metadata like generated_at and sdk_min_version.
API Endpoints
Download Current Bundle
x-api-key header.
Returns the most recently generated bundle, wrapped in data. This is the endpoint you call in your CI/CD pipeline. When no bundle has been generated yet, it returns {"data": null, "message": "No bundle published yet"} with HTTP 200.
Response:
appdna-config.json, so save data.content (plus bundle_version), not the raw response:
jq -e exits non-zero when the request fails or no bundle exists, so the step fails. The shell creates the > target before jq runs, so writing straight to appdna-config.json would leave it empty on a failure; writing to a temporary file and moving it only on success keeps the previous file intact.
Check Bundle Version
x-api-key header.
Lightweight version check that returns metadata without the full content payload. Useful for polling or conditional downloads in CI.
This endpoint returns only metadata (no
content field), making it ideal for checking whether you need to download a new bundle before every build.Force Regenerate Bundle
Authorization: Bearer header, plus x-tenant-id / x-app-id.
Forces the server to regenerate the config bundle from the current published state of all modules. Returns the newly generated bundle, always as a new version.
Publishing regenerates the bundle on its own, usually within about 20 seconds. Call this endpoint when a build must not wait for that, or to rebuild without publishing.
Bundle Version History
Authorization: Bearer header, plus x-tenant-id / x-app-id.
Returns a paginated list of previous bundle versions with metadata. Useful for auditing or rollback workflows.
CI/CD Integration
Adding a config bundle download step to your build pipeline takes three steps:1
Add a build step that downloads the bundle
Call
GET /api/v1/sdk/config-bundle with your SDK API key and save data.content (plus bundle_version) to appdna-config.json.2
Save the file to the platform-specific assets directory
Each platform expects the file in a specific location (see platform-specific examples below).
3
Build your app as normal
The native SDK reads the file once its bootstrap request has succeeded or failed, and uses it for any section that has no cached config. A remote fetch replaces it.
Build Script Examples
- Fastlane (iOS)
- Gradle (Android)
- Flutter
- React Native
Add a lane or a pre-build action to your Make sure
Fastfile:Resources/appdna-config.json is added to your Xcode project under Build Phases > Copy Bundle Resources.Bundle + Assets (ZIP)
Settings → SDK → Config Bundle in the Console has two downloads. JSON Only saves the sameappdna-config.json the scripts above write. Bundle + Assets (ZIP) saves appdna-config-bundle.zip, which holds:
appdna-config.json— the same bundle, except that each image URL whose image could be downloaded is rewritten to a relative path,appdna-assets/<file>;appdna-assets/— those images.
So embed the ZIP’s
appdna-config.json only in an iOS app. For Android, embed the JSON Only file (or the file the scripts above download), which keeps the original image URLs; those images load from the network once the device is online. The ZIP is a Console download only: CI scripts download the JSON bundle with GET /api/v1/sdk/config-bundle. Because the ZIP downloads every image again, it is limited to 10 downloads per user every 10 minutes and 30 per app every hour; past that the Console gets HTTP 429 with a Retry-After header. A download that is refused before it starts (no SDK key yet, or no bundle generated) does not count. The server also builds at most two ZIP exports at a time; while two are running, another request gets HTTP 429 with Retry-After: 30 and error.code EXPORT_BUSY, and that request does not count against the limits. The JSON downloads are not affected.
Version Checking
The SDK does not download bundles or callGET /api/v1/sdk/config-bundle/version; it fetches live config from Firestore (see Offline-First Architecture). It reads bundle_version from the embedded file and, once it has loaded a file whose bundle_version is greater than 0, sends it in the device context of every event. With no file, or a file without bundle_version, events carry none. The server does not store that value, so the Console does not show which bundle installed builds carry. Use the version endpoint in CI to decide whether a download is needed.
Best Practices
-
Let your last publish settle before the build downloads. Publishing regenerates the bundle, usually within about 20 seconds; a build that downloads sooner gets the previous bundle. To be certain, call
POST /api/v1/sdk/config-bundle/generate(or Regenerate) right before the build. -
Use the
x-api-keyheader, not Bearer tokens. The config bundle download endpoint accepts SDK API keys, which are scoped to your app and safe to use in CI environments. -
Fail the build if the download fails. Use
curl -sf(silent + fail on error) or equivalent so your build breaks visibly if the download fails, rather than shipping an outdated or missing bundle. -
Store the API key as a CI secret. Never hardcode
APPDNA_SDK_KEYin your build scripts. Use your CI provider’s secret management (GitHub Actions secrets, Bitrise secrets, etc.). -
Check the content, not the size. A bundle of an app with nothing published is about 200 bytes — every section empty — and still a valid file, so size alone proves nothing. The
jq -efilter above already fails the step when the request fails or no bundle exists; to be stricter, check that the sections you rely on are present, for examplejq -e '.paywalls | length > 0' appdna-config.json.

