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

# Swift API reference

Every public symbol in the `AppstackSDK` Swift package. For installation and
integration guidance, see the [Swift SDK guide](/SDKs/swift).

Current stable release: **4.7.1**, published 11 September 2026. See the [changelog](/changelog/swift) for everything that changed.

## AppstackAttributionSdk

The SDK entry point. Reach it through `AppstackAttributionSdk.shared`; it has no
public initializer.

```swift theme={null}
public class AppstackAttributionSdk: @unchecked Sendable

public static let shared: AppstackAttributionSdk
```

### configure(apiKey:logLevel:customerUserId:)

```swift theme={null}
@available(*, deprecated, renamed: "configure(apiKey:logLevel:customerUserId:)", message: "isDebug is no longer used; drop it from your configure() call. This overload will be removed in the next major version.") public func configure(apiKey appstackApiKey: String, isDebug: Bool, endpointBaseUrl: String? = nil, logLevel: LogLevel = .info, customerUserId: String? = nil)

@available(*, deprecated, renamed: "configure(apiKey:logLevel:customerUserId:)", message: "endpointBaseUrl is no longer used; drop it from your configure() call. This overload will be removed in the next major version.") public func configure(apiKey appstackApiKey: String, endpointBaseUrl: String?, logLevel: LogLevel = .info, customerUserId: String? = nil)

public func configure(apiKey appstackApiKey: String, logLevel: LogLevel = .info, customerUserId: String? = nil)
```

Starts the SDK. Call once, as early in app launch as possible. A repeat call is a
no-op, and its `customerUserId` is ignored on that second call. Use
[`setCustomerUserId(_:)`](#setcustomeruserid-_) to set the ID later.

<ParamField path="apiKey" type="String" required>
  Your app's Appstack API key, from the dashboard under app settings.
</ParamField>

<ParamField path="logLevel" type="LogLevel" default=".info">
  Console verbosity. One of `.off`, `.error`, `.info`, `.debug`. See [LogLevel](#loglevel).
</ParamField>

<ParamField path="customerUserId" type="String?" default="nil">
  Your identifier for the signed-in user, when it is already known at launch.
</ParamField>

<CodeGroup>
  ```swift AppDelegate theme={null}
  func application(
      _ application: UIApplication,
      didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
      AppstackAttributionSdk.shared.configure(apiKey: "your_api_key")
      return true
  }
  ```

  ```swift SwiftUI theme={null}
  @main
  struct MyApp: App {
      init() {
          AppstackAttributionSdk.shared.configure(apiKey: "your_api_key")
      }

      var body: some Scene {
          WindowGroup { ContentView() }
      }
  }
  ```
</CodeGroup>

<Accordion title="Deprecated overloads">
  Both remain callable and behave identically to the current overload, because the
  removed parameters are ignored. They will be removed in the next major version.

  ```swift theme={null}
  @available(*, deprecated, renamed: "configure(apiKey:logLevel:customerUserId:)")
  public func configure(
      apiKey appstackApiKey: String,
      isDebug: Bool,
      endpointBaseUrl: String? = nil,
      logLevel: LogLevel = .info,
      customerUserId: String? = nil
  )

  @available(*, deprecated, renamed: "configure(apiKey:logLevel:customerUserId:)")
  public func configure(
      apiKey appstackApiKey: String,
      endpointBaseUrl: String?,
      logLevel: LogLevel = .info,
      customerUserId: String? = nil
  )
  ```

  <ResponseField name="isDebug" type="removed">
    No longer used. Drop it from your `configure()` call.
  </ResponseField>

  <ResponseField name="endpointBaseUrl" type="removed">
    No longer used. Drop it from your `configure()` call.
  </ResponseField>
</Accordion>

### setCustomerUserId(\_:)

```swift theme={null}
public func setCustomerUserId(_ customerUserId: String?)
```

Sets your identifier for the signed-in user. Applies to every event sent from here
on, including ones still buffered. Safe from any thread, before or after
`configure()`; last write wins.

<ParamField path="customerUserId" type="String?" required>
  Your identifier for the signed-in user. Set it as soon as it is known, and at
  least one event has to follow for it to take effect.
</ParamField>

```swift theme={null}
AppstackAttributionSdk.shared.setCustomerUserId("user_123")  // once login reveals it
```

### sendEvent(event:name:parameters:)

```swift theme={null}
public func sendEvent(event: EventType, name: String? = nil, parameters: [String : Any]? = nil)
```

Fires an event. Safe to call before `configure()` completes: events arriving early
are buffered and flushed once configuration finishes. The buffer is bounded, so
events sent long before `configure()` may be dropped.

<ParamField path="event" type="EventType" required>
  The event to send. Prefer a standard case over `.CUSTOM` wherever one fits. See
  [EventType](#eventtype).
</ParamField>

<ParamField path="name" type="String?" default="nil">
  The event name. Required when `event` is `.CUSTOM`; ignored otherwise, since
  standard cases carry their own wire name.
</ParamField>

<ParamField path="parameters" type="[String: Any]?" default="nil">
  Event properties. For revenue events send `revenue` (or `price`) and `currency`.
  Matching parameters (`email`, `name`, `phone_number`, `date_of_birth`, `gender`)
  are encrypted before they are used for attribution matching — on device on
  iOS 17+, and server-side on iOS 15–16. See
  [enhanced app campaigns](/enhanced-app-campaigns).
</ParamField>

```swift theme={null}
// Standard event
AppstackAttributionSdk.shared.sendEvent(event: .LOGIN)

// Revenue event
AppstackAttributionSdk.shared.sendEvent(
    event: .PURCHASE,
    parameters: ["revenue": 29.99, "currency": "USD"]
)

// Custom event, `name` is required
AppstackAttributionSdk.shared.sendEvent(
    event: .CUSTOM,
    name: "onboarding_finished",
    parameters: ["variant": "b"]
)
```

### getAppstackId()

```swift theme={null}
public func getAppstackId() -> String?
```

Returns the stable local Appstack identity for this install. Safe to call before
`configure()`: the ID is minted on first read if absent, so integrations that read
it at launch (RevenueCat, Superwall) never receive `nil` because of a race with
`configure()`.

<ResponseField name="returns" type="String?">
  The install's Appstack ID.
</ResponseField>

```swift theme={null}
let appstackId = AppstackAttributionSdk.shared.getAppstackId()
```

### isSdkDisabled()

```swift theme={null}
public func isSdkDisabled() -> Bool
```

Whether the SDK is currently disabled. It reports `true` before `configure()` has
run, and whenever remote configuration has switched it off.

<ResponseField name="returns" type="Bool">
  `true` when the SDK is disabled and no events will be sent.
</ResponseField>

### handleUniversalLink(\_:options:)

```swift theme={null}
@discardableResult public func handleUniversalLink(_ userActivity: NSUserActivity, options: LinkOptions = LinkOptions()) -> UniversalLinkResult?

@discardableResult public func handleUniversalLink(_ url: URL, options: LinkOptions = LinkOptions()) -> UniversalLinkResult?
```

Handles a tapped Universal Link for an app that is already installed. Parses the
`deeplinkId` and query parameters directly off the tapped URL, with no network round
trip, because the URL already carries everything attached to the link. Safe to call
before `configure()`.

Requires an absolute HTTPS URL with a host and the standard link path
`/{deeplinkId}`, on a custom domain provisioned for the app.

<Note>
  This tap is intercepted by the OS, so no click event is recorded server-side. If
  you want to count it as re-engagement, call `sendEvent(...)` yourself. This method
  does not.
</Note>

<ParamField path="url" type="URL" required>
  The tapped link. The `NSUserActivity` overload extracts this for you, so pass the
  activity straight from `application(_:continue:restorationHandler:)` or
  `scene(_:continue:)`.
</ParamField>

<ParamField path="options" type="LinkOptions" default="LinkOptions()">
  Optional host filtering. See [LinkOptions](#linkoptions).
</ParamField>

<ResponseField name="returns" type="UniversalLinkResult?">
  The parsed link, or `nil` when the activity is not a Universal Link, the URL
  structure is unsupported, or the host is not allowed.
</ResponseField>

<CodeGroup>
  ```swift UIKit theme={null}
  func application(
      _ application: UIApplication,
      continue userActivity: NSUserActivity,
      restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
  ) -> Bool {
      guard let link = AppstackAttributionSdk.shared.handleUniversalLink(userActivity) else {
          return false
      }
      route(to: link.deeplinkId, params: link.queryParams)
      return true
  }
  ```

  ```swift SwiftUI theme={null}
  .onOpenURL { url in
      guard let link = AppstackAttributionSdk.shared.handleUniversalLink(url) else { return }
      route(to: link.deeplinkId, params: link.queryParams)
  }
  ```
</CodeGroup>

### getAttributionParams()

```swift theme={null}
public func getAttributionParams() async -> [String : Any]?
```

Waits for the initial attribution match to finish, then returns the parameters.
Suspends rather than blocking a thread. Prefer this over a fixed delay after launch.
The match runs at most once per install, at configure time.

If `configure()` has not been called, it returns immediately rather than waiting for
a launch that may never come.

<ResponseField name="returns" type="[String: Any]?">
  Attribution parameters, always carrying the reserved
  [`attributionMatchStatusKey`](#attributionmatchstatuskey). Never `nil` in practice:
  the optional is kept so existing `if let` and `??` call sites keep compiling.
</ResponseField>

```swift theme={null}
let params = await AppstackAttributionSdk.shared.getAttributionParams()
let status = params?[AppstackAttributionSdk.attributionMatchStatusKey] as? String
```

### attributionMatchStatusKey

```swift theme={null}
public static let attributionMatchStatusKey: String  // "appstack_match_status"
```

Reserved key present in every [`getAttributionParams()`](#getattributionparams)
result. It distinguishes an organic install from a skipped match and from a failed
request, rather than all three arriving as an empty result.

| Value               | Meaning                                                                                                                                      |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `matched`           | The install is attributed and parameters are present.                                                                                        |
| `matched_no_params` | A click matched, but the link carried no recognised tracking parameters.                                                                     |
| `organic`           | Confirmed no attribution.                                                                                                                    |
| `skipped`           | No match was attempted for this launch. Terminal for the life of the install.                                                                |
| `failed`            | Attempted but unresolved (offline, timeout, or a server error). Retried in-session and next launch, so re-read rather than assuming organic. |
| `not_configured`    | Read before `configure()` completed, or the SDK is disabled.                                                                                 |

### deleteUserData()

```swift theme={null}
public func deleteUserData() async throws
```

Requests deletion of this install's user data. On success, locally cached attribution
data is cleared too. Throws if the request fails.

```swift theme={null}
try await AppstackAttributionSdk.shared.deleteUserData()
```

## AppstackASAAttribution

Apple Search Ads attribution, kept separate from the main SDK so apps that do not run
Apple Ads never link it.

```swift theme={null}
@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, *)
final public class AppstackASAAttribution: @unchecked Sendable

public static let shared: AppstackASAAttribution
```

### enableAppleAdsAttribution()

```swift theme={null}
final public func enableAppleAdsAttribution()
```

Starts Apple Search Ads attribution. Call it after the App Tracking Transparency
prompt resolves, so the SDK sees the final authorization status.

```swift theme={null}
if #available(iOS 15.0, *) {
    ATTrackingManager.requestTrackingAuthorization { _ in
        AppstackASAAttribution.shared.enableAppleAdsAttribution()
    }
}
```

### disableASAAttributionTracking()

```swift theme={null}
final public func disableASAAttributionTracking()
```

Stops Apple Search Ads attribution.

## Types

### EventType

```swift theme={null}
public enum EventType: String, CaseIterable
```

Standard attribution events. Case names follow the SNAKE\_CASE convention used by
mobile measurement partners, and the raw value sent over the wire is the case name
itself, so `EventType.ADD_TO_CART.rawValue` is `"ADD_TO_CART"`. Where two names are
synonymous, both are provided for compatibility with existing integrations.

| Group        | Cases                                                                                         |
| ------------ | --------------------------------------------------------------------------------------------- |
| Lifecycle    | `INSTALL`                                                                                     |
| Auth         | `LOGIN`, `SIGN_UP`, `REGISTER`                                                                |
| Monetization | `PURCHASE`, `ADD_TO_CART`, `ADD_TO_WISHLIST`, `INITIATE_CHECKOUT`, `START_TRIAL`, `SUBSCRIBE` |
| Games        | `LEVEL_START`, `LEVEL_COMPLETE`                                                               |
| Engagement   | `TUTORIAL_COMPLETE`, `SEARCH`, `VIEW_ITEM`, `VIEW_CONTENT`, `SHARE`                           |
| Catch-all    | `CUSTOM`                                                                                      |

<Note>
  `INSTALL` is tracked by the SDK itself. You do not need to send it.
</Note>

<ResponseField name="eventName" type="String">
  The event name as sent over the wire.
</ResponseField>

<ResponseField name="allCases" type="[EventType]">
  Every case, from `CaseIterable`.
</ResponseField>

### LogLevel

```swift theme={null}
public enum LogLevel: Int {
    case off
    case error
    case info
    case debug
}
```

Console verbosity, passed to `configure(apiKey:logLevel:customerUserId:)`. Defaults
to `.info`.

### LinkOptions

```swift theme={null}
public struct LinkOptions {
    public let allowedHosts: Set<String>?
    public init(allowedHosts: Set<String>? = nil)
}
```

Optional filtering for incoming links. Requires no network access or SDK
initialization.

<ResponseField name="allowedHosts" type="Set<String>?" default="nil">
  Hosts to accept. A link on any other host returns `nil`. `nil` accepts any host.
  This does not verify domain ownership: your app decides what to route.
</ResponseField>

### UniversalLinkResult

```swift theme={null}
public struct UniversalLinkResult {
    public let deeplinkId: String?
    public let queryParams: [String: String]
    public let url: URL
}
```

A tapped Universal Link on an already-installed app, parsed entirely from the URL.

<ResponseField name="deeplinkId" type="String?">
  The Appstack deep link ID, when the URL carries one.
</ResponseField>

<ResponseField name="queryParams" type="[String: String]">
  Query parameters parsed off the tapped URL.
</ResponseField>

<ResponseField name="url" type="URL">
  The URL that was handled.
</ResponseField>
