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

# iOS SDK

> Install and use the Biqli iOS SDK for Universal Links and explicit deferred handoff.

<Warning>
  The iOS SDK is in **Public Beta**. It supports iOS 15 and later. Validate deferred installation with a TestFlight or App Store build before production use.
</Warning>

The open-source Swift package supports Universal Links, user-initiated deferred handoff, protected pending state, idempotent retries, and optional consent-gated probabilistic matching.

## Requirements

* iOS 15 or later
* Xcode 15 or later
* Swift 5.9 or later

## Install with Swift Package Manager

In Xcode, select **File → Add Package Dependencies** and enter:

```text theme={null}
https://github.com/BiqliLLC/biqli-ios.git
```

Select **Up to Next Major Version** from `1.0.0`, then add the `Biqli` product to the application target.

For a manifest:

```swift theme={null}
dependencies: [
    .package(
        url: "https://github.com/BiqliLLC/biqli-ios.git",
        from: "1.0.0"
    ),
]
```

Add `.product(name: "Biqli", package: "biqli-ios")` to the target dependency list.

* [Source and releases](https://github.com/BiqliLLC/biqli-ios)
* [Apple Universal Link documentation](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app)

## Configure

```swift theme={null}
import Biqli

try await Biqli.configure(
    appId: "biq_mapp_01ARZ3NDEKTSV4RRFFQ69G5FAV",
    publishableKey: "biqli_mobile_pk_your_public_mobile_key"
)
```

| Parameter                      | Type      | Default                 | Description                                                     |
| :----------------------------- | :-------- | :---------------------- | :-------------------------------------------------------------- |
| `appId`                        | `String`  | Required                | Public Mobile App ID beginning with `biq_mapp_`.                |
| `publishableKey`               | `String`  | Required                | Public app-scoped key beginning with `biqli_mobile_pk_`.        |
| `apiBaseURL`                   | `URL`     | `https://biq.li/api/v1` | HTTPS API base without credentials, query, or fragment.         |
| `consentRequired`              | `Bool`    | `false`                 | Stop before networking until the caller grants consent.         |
| `probabilisticMatchingEnabled` | `Bool`    | `false`                 | Request optional, clearly labeled probabilistic matching.       |
| `attributionDomain`            | `String?` | `nil`                   | Required valid hostname when probabilistic matching is enabled. |
| `diagnosticsEnabled`           | `Bool`    | `false`                 | Enable bounded SDK status callbacks.                            |
| `diagnosticHandler`            | closure   | `nil`                   | Receives `BiqliDiagnostic` values.                              |

The attached verified domain must match `attributionDomain`. Never embed a `biqli_...` secret workspace API key.

## Add the Universal Link entitlement

Add the **Associated Domains** capability to the app target and include:

```text theme={null}
applinks:go.example.com
```

Use the exact hostname without a scheme, path, query, or trailing slash. Each hostname needs its own association entry and Apple App Site Association file.

## SwiftUI lifecycle integration

Universal Links arrive as browsing-web user activities, not as custom URL schemes:

```swift theme={null}
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
    guard let url = activity.webpageURL else { return }

    Task {
        await Biqli.handle(url: url)
        do {
            let result = try await Biqli.resolveAttribution()
            router.open(result.link?.route)
        } catch {
            // Keep startup non-blocking and retry pending work later.
        }
    }
}
```

You may also pass a known verified link directly as `resolveAttribution(deepLink: url)`.

## UIKit lifecycle integration

Forward `userActivity.webpageURL` from `application(_:continue:restorationHandler:)` or the corresponding scene delegate method:

```swift theme={null}
func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard
        userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let url = userActivity.webpageURL
    else { return false }

    Task {
        await Biqli.handle(url: url)
        _ = try? await Biqli.resolveAttribution()
    }
    return true
}
```

## Exact deferred handoff on iOS 16 and later

Use the system-backed SwiftUI control after the first launch:

```swift theme={null}
BiqliPasteButton(consentGranted: userHasConsented) { result in
    switch result {
    case .success(let attribution):
        router.open(attribution.link?.route)
        attribution.attributionReceipt.map(backend.verifyReceipt)
    case .failure(let error):
        show(error)
    }
}
```

UIKit can construct `BiqliPasteControl(consentGranted:completion:)`. Both use Apple's user-operated `UIPasteControl`; the SDK never polls or silently reads the pasteboard.

See [Apple's `UIPasteControl` reference](https://developer.apple.com/documentation/uikit/uipastecontrol)
for the platform control's user-initiated behavior.

## Explicit paste on iOS 15

Provide your own clearly labeled user-tapped paste action. After the person supplies a URL, call the actual SDK API:

```swift theme={null}
let result = try await Biqli.resolvePastedURL(
    pastedURL,
    consentGranted: userHasConsented
)
```

There is no `resolveFromPasteboard` API. The SDK accepts a complete HTTPS handoff URL containing a valid opaque `biqli_token`; malformed text produces `BiqliError.invalidHandoffURL`.

## Result model

`BiqliAttributionResult` contains `open`, optional `click`, optional `link`, optional `attribution`, optional `attributionReceipt`, and `requestId`. The attribution metadata is represented by the `JSONValue` enum, and dynamic values are `[String: String]`.

Match values are:

```text theme={null}
exact_app_link
exact_install_referrer
exact_handoff
probabilistic
none
```

An iOS integration normally observes `exact_app_link`, `exact_handoff`, `probabilistic`, or `none`.

## State, reinstall, and receipts

The app-instance ID, pending link or opaque handoff, pending idempotent request, and cached matched result use Keychain storage with this-device-only accessibility. A UserDefaults installation marker is removed by uninstall; when the marker is absent after reinstall, the SDK clears surviving Keychain state and creates a new app instance.

A matched first-open result is cached without its signed receipt. A later cold launch with no new link or handoff returns that cache without creating another open. Unmatched iOS first opens remain eligible for later recovery during the 24-hour first-open window.

The ten-minute receipt exists only on the live matched response. Send it to a trusted backend immediately if the operation needs proof.

## Retry and timeout behavior

* Request timeout: 5 seconds.
* Immediate attempts: at most three.
* Backoff between attempts: approximately 400 ms, then 800 ms, plus 0–250 ms random jitter.
* Immediate retries: transport errors, HTTP `429`, and HTTP `5xx`.
* Later-launch recovery: pending request, event ID, and idempotency key remain protected in Keychain.

The SDK does not perform an autonomous background retry loop. Call `resolveAttribution()` on a later cold launch.

## Consent and probabilistic matching

When `consentRequired` is `true`, a call with `consentGranted: false` throws `BiqliError.consentRequired` before networking.

To request probabilistic matching, both the Mobile App and SDK must enable it, `attributionDomain` must name the attached verified hostname, and consent must be granted. The wire request uses `probabilisticAllowed`; `probabilisticConsent` is not a supported field. An ambiguous candidate returns `none`.

## Diagnostics

The bounded diagnostic enum contains:

```text theme={null}
appLinkCaptured
resolverRetry
resolverMatched
resolverNoMatch
```

The callback never receives raw URLs, opaque tokens, mobile keys, or resolver payloads.

## Errors

| `BiqliError`               | Meaning                                                            |
| :------------------------- | :----------------------------------------------------------------- |
| `notConfigured`            | Configuration has not completed.                                   |
| `consentRequired`          | The configuration requires consent.                                |
| `invalidConfiguration`     | App ID, key, API base, or probabilistic hostname is invalid.       |
| `invalidHandoffURL`        | The user-supplied paste item is not a valid Biqli handoff URL.     |
| `invalidResponse`          | The resolver response is missing, oversized, or cannot be decoded. |
| `secureStorageUnavailable` | Protected SDK state could not be read or written.                  |
| `http(status:code:)`       | The server returned a terminal HTTP rejection.                     |
| `transport`                | All immediate retry attempts failed.                               |

Follow [Test and troubleshoot mobile links](/developers/mobile/testing-troubleshooting) for simulator, device, and TestFlight coverage.
