> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ada.cx/_mcp/server.

# Getting started

The Ada iOS SDK is a framework that you can use to embed your Ada AI agent into your native iOS application.

It also supports actions and settings that you can use to customize the behavior of your AI Agent. For example, you might want to set the language, or pass metadata. The [iOS SDK reference](/messaging/ios/sdk-api-reference) covers these options and more.

> **Note**
>
> You'll need an active Ada AI Agent handle and access to the Ada Dashboard to use the SDK. To gain access, please reach out to an Ada Account Manager.

> **Note**
>
> This guide covers the new Ada iOS SDK. If you are migrating from the existing SDK (`AdaEmbedFramework`), see [Upgrade from the Existing iOS SDK](#upgrade-from-the-existing-ios-sdk) below.

## Compatibility

| Requirement | Minimum Version                                                  |
| ----------- | ---------------------------------------------------------------- |
| iOS         | 15.4                                                             |
| Swift       | Swift 6.1 compiler or later. Swift 5 language mode is supported. |
| Xcode       | Xcode 16.3 or later                                              |

Ada supports iOS 15.4 or later. Ada tests new iOS versions when they become generally available and continues testing the two versions behind the current version.

## Install the iOS SDK

You can install the Ada iOS SDK using Swift Package Manager, CocoaPods, Carthage, or manually. **Swift Package Manager is the recommended installation path.**

### Install using Swift Package Manager (Recommended)

**Via Xcode:**

1. Open **File > Add Package Dependencies...**
2. Enter the repository URL: `https://github.com/ada-cx-public/messaging-ios.git`
3. Choose the release version you want to ship.
4. Add the `AdaMessaging` product to your app target.

**Via `Package.swift`:**

**`Swift`**

```swift Swift
dependencies: [
    .package(url: "https://github.com/ada-cx-public/messaging-ios.git", from: "<version>"),
],
targets: [
    .target(
        name: "YourTarget",
        dependencies: ["AdaMessaging"]
    ),
]
```

Replace `<version>` with the latest release tag from the [releases page](https://github.com/ada-cx-public/messaging-ios/releases).

### Install using CocoaPods

1. Add `AdaMessaging` to your Podfile.

2. Install the pod using: `pod install`.

**`Swift`**

```swift Swift
# Podfile
platform :ios, '15.4'

target 'MyApp' do
  use_frameworks!

  # Pods for MyApp
  pod "AdaMessaging", :git => "https://github.com/ada-cx-public/messaging-ios", :tag => "<version>"

end
```

Replace `<version>` with the latest release tag from the [releases page](https://github.com/ada-cx-public/messaging-ios/releases). The `:tag` pin is exact, so update it when you upgrade.

### Install using Carthage

Add the following to your Cartfile:

**`Swift`**

```swift Swift
github "ada-cx-public/messaging-ios" ~> <version>
```

Replace `<version>` with the latest release tag from the [releases page](https://github.com/ada-cx-public/messaging-ios/releases).

Then run:

```bash
carthage update --use-xcframeworks
```

Add `AdaMessaging.xcframework` to your app target in Xcode.

### Install iOS SDK manually

1. Download `AdaMessaging.xcframework.zip` from the latest release [here](https://github.com/ada-cx-public/messaging-ios/releases).
2. Right-click on the project file in Xcode, then click **Add Files to *MyProjectName***. Ensure that the **groups** option is selected.
3. Set your **Deployment Target** to 15.4 or later.
4. In your target's **General** settings, add `AdaMessaging.xcframework` under **Frameworks, Libraries, and Embedded Content** with **Embed & Sign** selected.

## Import the framework

Once you've installed the Ada iOS SDK, you're ready to use it in your app!

1. Import `AdaMessaging` into your controller.
2. Create an instance of the `AdaWebHost`, as in the example below, replacing `my-bot` with the actual name of your bot.

**`Swift`**

```swift Swift
import AdaMessaging

// ...

lazy var adaWebHost = AdaWebHost(handle: "my-bot")
```

> **Note**
>
> The lazy property prevents AdaWebHost from initializing until the property is used. Use of this property may help to prevent unwanted end users from being created in the background.

You can also pass optional configuration at initialization:

**`Swift`**

```swift Swift
let adaWebHost = AdaWebHost(
    handle: "my-bot",
    language: "en",
    metafields: [
        "plan": "pro",
        "signedIn": true,
    ]
)
```

> **Note**
>
> Most apps only need `handle`. Leave `cluster` and `domain` unset unless Ada tells you your AI agent is hosted on a non-default regional cluster or custom domain.

## Choose the web runtime

`AdaWebHost` can mount one of two web runtimes inside its WebView:

* `.legacy` (the default) — the same runtime the existing Chat SDK loads. Apps migrating from `AdaEmbedFramework` keep this default and see no behavior change.
* `.messaging` — the Messaging runtime. Identity tokens, headless mode, and `sendMessage` require it.

To mount the Messaging runtime, set both `environment` and `webSdk`:

**`Swift`**

```swift Swift
let adaWebHost = AdaWebHost(
    handle: "my-bot",
    environment: .production,
    webSdk: .messaging
)
```

> **Note**
>
> The Messaging runtime requires an explicit `environment`. If you set `webSdk: .messaging` without `environment`, the host falls back to the legacy path.

## Launch Ada

Finally, launch Ada using one of the four opening methods:

* `launchModalWebSupport` — Presents the chat as a modal overlay.
* `launchNavWebSupport` — Pushes the chat onto the navigation stack.
* `launchInjectingWebSupport` — Injects the chat view into an existing view.
* `launchHeadlessWebSupport` — Runs the chat offscreen, with no Ada UI. See [Run headless and build your own UI](#run-headless-and-build-your-own-ui).

**`Swift`**

```swift Swift
adaWebHost.launchModalWebSupport(from: self)
```

## Authenticate end users with an identity token

If your app has signed-in users, you can authenticate them to Ada with a short-lived identity token. This requires the [Messaging runtime](#choose-the-web-runtime).

1. Your backend mints a token for the signed-in user's end user ID with the Ada Platform API (`POST /v2/auth/tokens/`), authenticated with your Ada API key. Tokens are single use and expire after 15 minutes.
2. Your backend returns the token to your app over your own authenticated channel.
3. Your app passes the token to `AdaWebHost` at initialization.

**`Swift`**

```swift Swift
let adaWebHost = AdaWebHost(
    handle: "my-bot",
    environment: .production,
    webSdk: .messaging,
    identityToken: token
)
```

The SDK exchanges the token once, when the session starts. Because tokens are single use, mint a fresh token for each new `AdaWebHost` you create.

> **Warning**
>
> Treat the identity token as a credential. Never log it, and never store it on the device. Never mint tokens from the app itself; that would require shipping your Ada API key in the app.

The SDK keeps the token out of every URL and never persists it. If the exchange fails, the runtime emits `ada:identity_token:error` to your `eventCallbacks`. For an expired token, it also emits `ada:identity_token:expired`. See [identityToken](/messaging/ios/sdk-api-reference#identitytoken) in the reference.

## Run headless and build your own UI

Headless mode runs the conversation in a hidden WebView while your app renders its own UI with native components. Use it for an unread badge, a custom chat screen, a search bar, or a command palette.

Create the host with `headless: true` and `enableProgrammaticControl: true`, then launch it without presenting UI. For example, to drive a native unread badge:

**`Swift`**

```swift Swift
var badgeCount = 0

let adaWebHost = AdaWebHost(
    handle: "my-bot",
    eventCallbacks: [
        "ada:message:received": { _ in
            badgeCount += 1
            // Show the unread badge (red dot) in your native UI.
        },
        "ada:message:sent": { _ in
            // Clear the badge, update your native transcript.
        }
    ],
    environment: .production,
    webSdk: .messaging,
    enableProgrammaticControl: true,
    headless: true
)

// Run offscreen. The SDK creates and retains a hidden, zero-sized container.
adaWebHost.launchHeadlessWebSupport()

// Drive the conversation from your native UI.
adaWebHost.sendMessage("Where is my order?")
```

The `ada:message:received` event fires for each message from the AI Agent or a human agent. The `ada:message:sent` event fires for each message the end user sends. Both events require `enableProgrammaticControl: true`.

> **Note**
>
> `launchHeadlessWebSupport` requires `webSdk: .messaging`, and stops the program with a precondition failure on the legacy runtime. It also forces `headless` to `true`: when the host was created with `headless: false`, the SDK rebuilds the WebView, which costs an extra load. Set both at initialization, as in the example above.

> **Warning**
>
> Events arrive only while your app runs in the foreground.
> iOS suspends the WebView with your app, so headless mode is not a background service.

Use [webhooks and your push provider](/docs/channels/messaging/push-notifications) to send push notifications outside the app lifecycle.
See [Headless session lifecycle](/messaging/ios/sdk-api-reference#headless-session-lifecycle) for all constraints.

## Runtime commands

You can interact with the Ada chat instance at runtime using the following methods.

### Set metadata

Use `MetaFields.Builder` to pass public or sensitive metadata to Ada:

**`Swift`**

```swift Swift
let publicFields = MetaFields.Builder()
    .setField(key: "plan", value: "pro")
    .setField(key: "signedIn", value: true)

let sensitiveFields = MetaFields.Builder()
    .setField(key: "authToken", value: "secure-session-token")

adaWebHost.setMetaFields(builder: publicFields)
adaWebHost.setSensitiveMetaFields(builder: sensitiveFields)
```

> **Note**
>
> Dictionary overloads for `setMetaFields` and `setSensitiveMetaFields` still exist for backward compatibility but are deprecated. Use `MetaFields.Builder` for all new code.

### Set language

**`Swift`**

```swift Swift
adaWebHost.setLanguage(language: "fr")
```

### Set device token

**`Swift`**

```swift Swift
adaWebHost.setDeviceToken(deviceToken: "apns-device-token")
```

### Send a message

**`Swift`**

```swift Swift
adaWebHost.sendMessage("Where is my order?")
```

> **Note**
>
> `sendMessage` requires the [Messaging runtime](#choose-the-web-runtime) and `enableProgrammaticControl: true`. Calls made before the runtime is ready are queued and dispatched when it becomes ready.

### Reset the chat

**`Swift`**

```swift Swift
adaWebHost.reset(
    language: "en",
    metaFields: publicFields,
    sensitiveMetaFields: sensitiveFields,
    resetChatHistory: true
)
```

### Delete chat history

**`Swift`**

```swift Swift
adaWebHost.deleteHistory()
```

## Info.plist permissions

If your AI Agent flow allows users to upload images, videos, or use the camera, add the corresponding usage descriptions to your app's `Info.plist`:

| Key                              | When Required            |
| -------------------------------- | ------------------------ |
| `NSCameraUsageDescription`       | Camera capture uploads   |
| `NSPhotoLibraryUsageDescription` | Photo library uploads    |
| `NSMicrophoneUsageDescription`   | Video capture with audio |

## Upgrade from the existing iOS SDK

If you are migrating from the existing `AdaEmbedFramework`, the new Messaging SDK is designed as a drop-in replacement. `AdaWebHost` remains the main public class — most apps only need a dependency swap and an import rename.

### Migration steps

1. Replace the old dependency with `AdaMessaging`.
2. Rename the framework import from `AdaEmbedFramework` to `AdaMessaging`.
3. Keep your existing `AdaWebHost` usage as-is.

### Side-by-side mapping

| Existing                        | Messaging SDK              |
| ------------------------------- | -------------------------- |
| `AdaEmbedFramework`             | `AdaMessaging`             |
| `AdaEmbedFramework.xcframework` | `AdaMessaging.xcframework` |
| `pod "AdaEmbedFramework"`       | `pod "AdaMessaging"`       |
| `import AdaEmbedFramework`      | `import AdaMessaging`      |

### Before / after: imports

**`Swift`**

```swift Swift
// Before
import AdaEmbedFramework

// After
import AdaMessaging
```

### Before / after: CocoaPods

**`Swift`**

```swift Swift
# Before
pod "AdaEmbedFramework"

# After
pod "AdaMessaging", :git => "https://github.com/ada-cx-public/messaging-ios", :tag => "<version>"
```

### Use MetaFields.Builder for new code

Dictionary overloads for `setMetaFields`, `setSensitiveMetaFields`, and some `reset` shapes still exist for backward compatibility but are deprecated. For all new code, prefer `MetaFields.Builder`:

**`Swift`**

```swift Swift
let publicFields = MetaFields.Builder()
    .setField(key: "plan", value: "pro")
    .setField(key: "signedIn", value: true)

let sensitiveFields = MetaFields.Builder()
    .setField(key: "authToken", value: "secure-session-token")

adaWebHost.setMetaFields(builder: publicFields)
adaWebHost.setSensitiveMetaFields(builder: sensitiveFields)
adaWebHost.reset(
    language: "en",
    metaFields: publicFields,
    sensitiveMetaFields: sensitiveFields,
    resetChatHistory: true
)
```

## Release checklist

Before shipping your integration or migration to production:

* Verify your real production AI Agent handle launches successfully
* Test the exact presentation mode you ship (modal, navigation push, inline, or headless)
* Confirm any event logging still receives SDK events
* Test device token storage in the end user's sensitive metadata. See [Push notifications](/docs/channels/messaging/push-notifications) for webhook delivery.
* Test `reset()` and `deleteHistory()` if your app exposes those actions
* If you use identity tokens, verify the happy path and confirm your code handles `ada:identity_token:error` and `ada:identity_token:expired`
* If you use headless mode, verify your native UI updates from `eventCallbacks` and that `sendMessage` delivers