> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Config Bundle

> Versioned configuration bundles for SDK caching

## Overview

Config Bundles are versioned JSON payloads that contain all the configuration the SDK needs to render onboarding flows, paywalls, feature flags, remote config, and in-app messages. The SDK downloads the bundle at launch and caches it locally, polling for updates periodically.

***

## Endpoints

### Download Latest Bundle

```
GET /api/v1/sdk/config-bundle
```

**Authentication:** SDK Key (`x-api-key` header)

Returns the full config bundle including all content. Use this when the SDK needs the complete configuration.

**Response:** The bundle is wrapped in a top-level `data` object.

```json theme={null}
{
  "data": {
    "bundle_version": 42,
    "sdk_min_version": "1.0.0",
    "size_bytes": 15234,
    "generated_at": "2026-02-19T10:00:00Z",
    "content": {
      "onboarding_flows": { ... },
      "paywalls": { ... },
      "remote_config": { ... },
      "feature_flags": { ... },
      "in_app_messages": { ... }
    }
  }
}
```

### Check Bundle Version

```
GET /api/v1/sdk/config-bundle/version
```

**Authentication:** SDK Key (`x-api-key` header)

Lightweight version check that returns metadata without the full content payload. Use this for polling.

**Response:** Metadata is wrapped in a top-level `data` object.

```json theme={null}
{
  "data": {
    "bundle_version": 42,
    "sdk_min_version": "1.0.0",
    "generated_at": "2026-02-19T10:00:00Z"
  }
}
```

<Info>
  This endpoint returns only metadata (no `content` field), making it ideal for frequent polling without unnecessary bandwidth usage.
</Info>

### Force Regenerate Bundle

```
POST /api/v1/sdk/config-bundle/generate
```

**Authentication:** Customer JWT (`Authorization: Bearer` header)

Forces the server to regenerate the config bundle. Returns the newly generated bundle. Use this after making configuration changes that you want to push immediately.

**Response:** `HTTP 201 Created`. Same shape as the Download endpoint — the bundle wrapped in a top-level `data` object.

### Bundle Version History

```
GET /api/v1/sdk/config-bundle/history
```

**Authentication:** Customer JWT (`Authorization: Bearer` header)

Returns a paginated list of previous bundle versions.

**Query parameters:**

| Parameter | Type     | Default | Description                            |
| --------- | -------- | ------- | -------------------------------------- |
| `limit`   | `number` | 20      | Number of versions to return (max 100) |
| `offset`  | `number` | 0       | Pagination offset                      |

### SDK Bootstrap

```
GET /api/v1/sdk/bootstrap
```

**Authentication:** SDK Key (`x-api-key` header)

Returns the org/app context and runtime settings the SDK needs to initialize. Rate-limited to 60 requests/minute per key; the response is client-cacheable for 24 hours.

**Response:**

```json theme={null}
{
  "orgId": "org-abc123",
  "appId": "app-def456",
  "firestorePath": "orgs/org-abc123/apps/app-def456",
  "settings": {
    "flushInterval": 30,
    "batchSize": 20,
    "configTTL": 300
  }
}
```

| Field                    | Type     | Description                                                      |
| ------------------------ | -------- | ---------------------------------------------------------------- |
| `orgId`                  | `string` | The organization (tenant) the key belongs to.                    |
| `appId`                  | `string` | The app the key belongs to.                                      |
| `firestorePath`          | `string` | The remote-config document path the SDK subscribes to.           |
| `settings.flushInterval` | `number` | Seconds between automatic event-batch flushes.                   |
| `settings.batchSize`     | `number` | Maximum events per upload batch.                                 |
| `settings.configTTL`     | `number` | Seconds the SDK may reuse cached config before re-bootstrapping. |

<Note>
  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.
</Note>

***

## Bundle Content Schema

The `content` field of the config bundle contains all published configurations organized by type:

```json theme={null}
{
  "content": {
    "onboarding_flows": {
      "<flow_id>": {
        "id": "flow-abc",
        "name": "Welcome Flow",
        "steps": [ ... ],
        "settings": { ... },
        "published_at": "2026-02-19T09:00:00Z"
      }
    },
    "paywalls": {
      "<paywall_id>": {
        "id": "pw-def",
        "name": "Premium Upgrade",
        "layout": "vertical",
        "plans": [ ... ],
        "cta": "Start Free Trial",
        "dismiss": true,
        "published_at": "2026-02-19T09:00:00Z"
      }
    },
    "remote_config": {},
    "feature_flags": {},
    "in_app_messages": {
      "<message_id>": {
        "id": "msg-ghi",
        "name": "Feature Announcement",
        "message_type": "modal",
        "content": { ... },
        "trigger_rules": { ... },
        "audience": { ... },
        "priority": 10
      }
    }
  }
}
```

### Content Types

| Key                | Description                                             |
| ------------------ | ------------------------------------------------------- |
| `onboarding_flows` | Step-by-step onboarding sequences with UI configuration |
| `paywalls`         | Paywall layouts, plans, and CTA configuration           |
| `remote_config`    | Key-value remote configuration pairs                    |
| `feature_flags`    | Boolean feature flag definitions with targeting rules   |
| `in_app_messages`  | Triggered in-app messages with audience targeting       |

***

## SDK Polling Pattern

The recommended SDK integration pattern minimizes bandwidth while keeping configuration fresh:

```
1. App launch  -->  GET /config-bundle  (full download)
2. Cache locally
3. Every N minutes  -->  GET /config-bundle/version  (lightweight check)
4. If bundle_version > cached version  -->  GET /config-bundle  (full download)
5. Update local cache
```

<Tabs>
  <Tab title="iOS (Swift)">
    ```swift theme={null}
    // AppDNA fetches and caches the config bundle automatically after configure().
    AppDNA.configure(
        apiKey: "adn_live_xxx",
        environment: .production,
        options: AppDNAOptions()
    )

    // The SDK polls for updates and caches the bundle locally. Server-driven
    // surfaces read from that cache — e.g. present an onboarding flow by ID:
    AppDNA.presentOnboarding(flowId: "welcome", from: viewController, delegate: nil)
    ```
  </Tab>

  <Tab title="Android (Kotlin)">
    ```kotlin theme={null}
    // AppDNA fetches and caches the config bundle automatically after configure().
    AppDNA.configure(
        context = this,
        apiKey = "adn_live_xxx",
        environment = Environment.PRODUCTION,
        options = AppDNAOptions()
    )

    // The SDK polls for updates and caches the bundle locally. Server-driven
    // surfaces read from that cache — e.g. present an onboarding flow by ID:
    AppDNA.presentOnboarding(activity = this, flowId = "welcome")
    ```
  </Tab>

  <Tab title="Flutter">
    ```dart theme={null}
    // AppDNA fetches and caches the config bundle automatically after configure().
    await AppDNA.configure(apiKey: 'adn_live_xxx', env: AppDNAEnvironment.production);

    // The SDK polls for updates and caches the bundle locally. Server-driven
    // surfaces read from that cache — e.g. present an onboarding flow by ID:
    await AppDNA.onboarding.present('welcome');
    ```
  </Tab>

  <Tab title="React Native">
    ```typescript theme={null}
    // AppDNA fetches and caches the config bundle automatically after configure().
    await AppDNA.configure('adn_live_xxx', 'production');

    // The SDK polls for updates and caches the bundle locally. Server-driven
    // surfaces read from that cache — e.g. present an onboarding flow by ID:
    await AppDNA.onboarding.present('welcome');
    ```
  </Tab>
</Tabs>

***

## Auto-Generation

The config bundle is automatically regenerated when any of the following actions occur in the dashboard:

* An onboarding flow is published or unpublished
* A paywall is published or updated
* An in-app message is published or updated
* Remote config or feature flags are modified

<Note>
  Manual regeneration via `POST /config-bundle/generate` is available but rarely needed. The platform handles regeneration automatically on publish.
</Note>

***

## CI/CD Integration

For zero-latency first launch, download the config bundle at build time and embed it as a fallback:

```bash theme={null}
# Download the latest bundle during your CI build
curl -s https://api.appdna.ai/api/v1/sdk/config-bundle \
  -H "x-api-key: $APPDNA_SDK_KEY" \
  -o appdna-config.json

# Include appdna-config.json in your app bundle
# The SDK will use this as a fallback if the network request fails on first launch
```

<Warning>
  The embedded bundle is a fallback only. The SDK will always attempt to fetch the latest bundle from the server on launch. Ensure your CI pipeline runs frequently enough that the embedded bundle does not become stale.
</Warning>

<Check>
  Your config bundle integration is working correctly when the SDK logs `ConfigBundle loaded (version: N)` during initialization and you see your published onboarding flows or paywalls rendering on device.
</Check>
