---
title: Initialization (iOS)
description: Initialize the Extentos iOS SDK with Extentos.create(config:), forward the Meta DAT auth callback via .onOpenURL and handleUrl, request speech authorization, and reach the typed sub-clients.
type: guide
platform: ios
vendor: meta
related:
  - /docs/sdk/ios/info-plist
  - /docs/sdk/ios/threading
  - /docs/sdk/ios/lifecycle
  - /docs/getting-started/with-agent
---

> The symbols below are verified against the current iOS source. The [agent-driven path](/docs/getting-started/with-agent) — `generateConnectionModule(platform: "ios")` — emits this wiring for you; package install: [Install](/docs/sdk/ios/install).

## Create the instance

The entry point is the `Extentos` enum. Call `Extentos.create(config:)` with an [`ExtentosConfig`](/docs/reference/ios-api), or `Extentos.default()` for defaults. Both return an `ExtentosGlasses` — the root object that owns every capability sub-client.

```swift
import GlassesCore

let glasses = Extentos.create(config: ExtentosConfig(
    appId: "com.example.myapp",
    transport: .auto,
    usedCapabilities: [.microphone, .speaker],
))
```

`usedCapabilities` is not decoration on iOS — it picks the transport (see the callout below). Declare your real footprint: `[.microphone, .speaker]` for a voice app, plus `.camera` / `.display` if you use them. Leave `debug` at its default unless you are driving the browser-simulator dev loop; `debug: true` opens a pending simulator socket and waits for the MCP bridge to bind it.

`transport: .auto` walks these rules in order, stopping at the first match:

| # | Condition | Result |
|---|---|---|
| 1 | `config.debug` **and** a session URL baked in by the scaffold | `.browserSim` |
| 2 | `config.debug` **and** `EXTENTOS_SESSION_URL` in the environment | `.browserSim` |
| 3 | `hasBondedMetaDevice` returns true | `.realMeta` |
| 4 | `config.debug` (no URL, no bonded device) | `.browserSim` in pending mode — waits for the MCP localhost bridge to bind it |
| 5a | `usedCapabilities` is non-empty and contains neither `.camera` nor `.display` | **`.systemAudio`** — the vendorless audio baseline |
| 5b | anything else (including an empty `usedCapabilities`) | `.realMeta`; `connect()` surfaces `NoEligibleDevice` when none are paired |

**Every `debug` above is the `ExtentosConfig.debug` field, not your Xcode build configuration.** It defaults to `false`, so a normal Debug-configuration run of a voice app skips rungs 1, 2 and 4 and lands on **5a** — the audio baseline — exactly like a release build. Set `debug: true` only when you are driving the browser-simulator dev loop; on a phone with no bridge running it parks on rung 4 and waits.

It never falls back to a simulator on its own.

<Callout type="warn">
  **On iOS, `usedCapabilities` decides your transport — it is not just connection-page decoration.** iOS has no API for enumerating bonded Bluetooth devices, so unlike Android there is no way to detect "are the glasses actually here?"; the declared footprint is the only honest signal. **An empty `usedCapabilities` resolves to real glasses**, which is right for a camera app but wrong for a voice app — on a phone with no glasses paired, `connect()` returns `NoEligibleDevice` instead of falling back to the phone's own mic and speaker. A voice-only app must declare its footprint explicitly:

  ```swift
  ExtentosConfig(usedCapabilities: [.microphone, .speaker])
  ```

  The empty default is deliberate — silently downgrading an app that declared nothing would strip camera support from existing integrations. See [runtime internals](/docs/sdk/ios/runtime-internals).
</Callout>

The simulator runs the *same* SDK code as hardware; see [transport vs app simulation](/docs/concepts/transport-vs-app). Hold a single instance for the app's lifetime.

> `Extentos.create(config:)` is the customer entry point. `Wearables.configure(...)` is an internal Meta DAT type — you never call it directly.

## Forward the auth callback (`.onOpenURL`)

Pairing with Meta Ray-Ban hands control to Meta's app and returns through your app's [custom URL scheme](/docs/sdk/ios/info-plist). Catch that callback in SwiftUI's `.onOpenURL` and forward it to `glasses.handleUrl(_:)`, which returns `true` when the SDK consumed the URL:

```swift
import SwiftUI

@main
struct MyApp: App {
    let glasses = Extentos.default()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .onOpenURL { url in
                    Task { _ = await glasses.handleUrl(url) }
                }
        }
    }
}
```

`handleUrl(_:)` is `async` and returns a `Bool`. The simulator transports return `false` unconditionally — only the real-Meta transport consumes the registration callback. The matching `Info.plist` `CFBundleURLTypes` entry must register the scheme, or iOS never delivers the URL.

## Request speech authorization (optional)

On-device transcription (`glasses.audio.transcriptions()`) uses `SFSpeechRecognizer`, which needs `NSSpeechRecognitionUsageDescription` in the [`Info.plist`](/docs/sdk/ios/info-plist) and a one-time user grant. The SDK requests it on first use, but you can surface the prompt at a controlled moment — e.g. during onboarding:

```swift
let granted = await Extentos.requestSpeechRecognitionAuthorization()
```

It returns `true` when authorized.

**The microphone is a separate grant, and it's yours.** Speech-recognition authorization does not cover audio capture. Request both before starting the assistant — iOS will otherwise prompt mid-session, or capture silence:

```swift
// AVAudioApplication is iOS 17+. On an iOS 16 target use
// AVAudioSession.sharedInstance().requestRecordPermission { granted in … }
let mic = await AVAudioApplication.requestRecordPermission()
let speech = await Extentos.requestSpeechRecognitionAuthorization()
guard mic, speech else { return }   // surface your own explanation
try await glasses.assistant.start(provider: .managed()) { … }
```

Both need their Info.plist strings (`NSMicrophoneUsageDescription`, `NSSpeechRecognitionUsageDescription`) or the app is terminated on first use — see [Info.plist](/docs/sdk/ios/info-plist). This is the iOS counterpart of the Android sequencing in the [voice-assistant guide](/docs/guides/voice-assistant#1-bootstrap); iOS needs no foreground service, only the `audio` background mode.

## The sub-client surface

`ExtentosGlasses` exposes typed capability sub-clients. Reach a primitive through its client — e.g. `glasses.camera.capturePhoto()`, `glasses.audio.transcriptions()`, `glasses.audio.speak(...)`.

| Sub-client | Always present | What it covers |
|---|---|---|
| `connection` | yes | Open / close the glasses connection; connection-state stream. |
| `camera` | yes | Photo capture, video capture, frame streams. |
| `audio` | yes | Transcription, audio capture, `speak`, audio chunks. |
| `runtime` | yes | The runtime event stream — `toggleChanged`, `coexistenceWarning`, `log`, `unrecognizedUtterance`, and `assistant` lifecycle events. |
| `toggles` | yes | The runtime capability gates. |
| `voice` | yes | Convenience surface over `audio.transcriptions()` that also announces your wake phrase to the simulator and connection page. |
| `telemetry` | yes | Telemetry events. |
| `observability` | yes | Wraps BYOK AI calls so they appear under the event log's `ai` chip. |
| `assistant` | yes | The voice-AI assistant runtime — see below. |
| `display` | yes | Declarative display trees for display-capable glasses (`show` / `clear`); `display.isAvailable` gates, and calls on no-display devices are silent no-ops. |
| `capabilities` | yes | `DeviceCapabilitySet` — flat yes/no hardware facts of the connected device; `display` flips live with the device. A value property, not a method client. |
| `conversation` | removed | Phase-3 conversation runtime — **removed** (Android in 1.4.0; never shipped in the published iOS package). Historical only. |
| `ai` | removed | Phase-3 BYOK LLM completion surface — **removed** alongside `conversation`. Historical only. |

The root object also exposes `usedCapabilities` (the declared capability footprint from `ExtentosConfig`) and a `connect()` convenience — what the generated bootstrap calls, equivalent to `connection.connect()`.

### Assistant

The `assistant` client is the voice-AI runtime. It routes through the Extentos managed gateway on both platforms — no API key in code. Start a session with the sugar form (builds the config inline), or use the raw form `createSession(config:)` + `session.start()`:

```swift
let session = try await glasses.assistant.start(provider: .managed()) {
    $0.instructions = "You are a helpful assistant on smart glasses."
}
```

An `AssistantSession` has a wake/sleep cycle: `start()` lands it Dormant, `wake()` opens the realtime connection, `sleep()` closes it but keeps the session (history, tools, hooks) ready for the next wake, `stop()` tears it down for good. See the [iOS API reference](/docs/reference/ios-api) for the full session surface and the [agent code examples](/docs/getting-started/with-agent) for canonical flows.

## Next

- [Info.plist setup](/docs/sdk/ios/info-plist) — the keys this wiring depends on.
- [Threading model](/docs/sdk/ios/threading) — how to call these `async` clients safely.
- [Lifecycle](/docs/sdk/ios/lifecycle) — tearing the instance down with `shutdown()`.
