Skip to main content

Swift SDK Integration Guide

This guide provides comprehensive instructions for integrating the Passage Swift SDK into your iOS application and implementing the required backend infrastructure.

Table of Contents

  1. Installation
  2. SDK Integration
  3. Backend Implementation
  4. API Reference
  5. Performance

Installation

Requirements

  • iOS 15.1+
  • Swift 5+
  • Xcode 13+

Add the package in Xcode:

  1. File → Add Package Dependencies
  2. Enter repository URL: https://github.com/tailriskai/passage-swift.git
  3. Choose version and add to your app target

Or in Package.swift:

dependencies: [
.package(url: "https://github.com/tailriskai/passage-swift.git", from: "0.0.33")
]

CocoaPods

Add to your Podfile:

pod 'PassageSDK'

Then run:

pod install

SDK Integration

Overview

To integrate the Passage Swift SDK, you need to complete these steps:

  1. Configure SDK → Initialize once in your app
  2. Backend Setup → Create intent token endpoint
  3. Get Token → Call your backend for intent token
  4. Open Passage → Launch connection flow with callbacks
  5. Handle Results → Process success/error responses

Quick Reference

Step 1: Configure SDK

import PassageSDK

// Configure once (typically in AppDelegate or SceneDelegate)
Passage.shared.configure(PassageConfig(debug: true))

Step 2: Get Intent Token

// Call your backend to get intent token
let url = URL(string: "https://your-api.com/intent-token")!
let (data, _) = try await URLSession.shared.data(from: url)
let response = try JSONDecoder().decode(IntentTokenResponse.self, from: data)

Step 3: Open Passage Flow

Passage.shared.open(
token: response.intentToken,
onConnectionComplete: { data in
print("Success: \(data.connectionId)")
},
onConnectionError: { error in
print("Error: \(error.error)")
}
)

Detailed Implementation

Complete iOS Integration

import SwiftUI
import PassageSDK

@main
struct MyApp: App {
init() {
// Configure Passage SDK once at app startup
Passage.shared.configure(
PassageConfig(
debug: true // Set to false in production
)
)
}

var body: some Scene {
WindowGroup {
ContentView()
}
}
}

struct ContentView: View {
@State private var isLoading = false
@State private var result: String = ""

var body: some View {
VStack(spacing: 20) {
Button("Connect Account") {
Task {
await connectAccount()
}
}
.disabled(isLoading)

if isLoading {
ProgressView("Connecting...")
}

Text(result)
.padding()
}
.padding()
}

func connectAccount() async {
isLoading = true
defer { isLoading = false }

do {
// 1. Get intent token from your backend
let intentToken = try await getIntentToken()

// 2. Open Passage with callbacks
await openPassage(token: intentToken)

} catch {
result = "Error: \(error.localizedDescription)"
}
}

func getIntentToken() async throws -> String {
let url = URL(string: "https://your-api.com/api/intent-token")!
let (data, _) = try await URLSession.shared.data(from: url)

struct TokenResponse: Codable {
let intentToken: String
}

let response = try JSONDecoder().decode(TokenResponse.self, from: data)
return response.intentToken
}

func openPassage(token: String) async {
// Present from main thread
await MainActor.run {
Passage.shared.open(
token: token,
presentationStyle: .modal, // or .fullScreen
onConnectionComplete: { [weak self] data in
DispatchQueue.main.async {
self?.result = "✅ Connected! ID: \(data.connectionId)"
print("History items: \(data.history.count)")

// Process the captured data
for item in data.history {
if let structuredData = item.structuredData {
print("Data: \(structuredData)")
}
}
}
},
onConnectionError: { [weak self] error in
DispatchQueue.main.async {
self?.result = "❌ Error: \(error.error)"
}
},
onDataComplete: { data in
print("Data complete: \(String(describing: data.data))")
},
onExit: { reason in
print("User exited: \(reason ?? "unknown")")
}
)
}
}
}

UIKit Integration

import UIKit
import PassageSDK

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

// Configure SDK
Passage.shared.configure(PassageConfig())
}

@IBAction func connectButtonTapped(_ sender: UIButton) {
Task {
await connectAccount()
}
}

func connectAccount() async {
do {
// Get intent token
let intentToken = try await getIntentToken()

// Open Passage flow
await MainActor.run {
Passage.shared.open(
token: intentToken,
from: self, // Specify presenting view controller
onConnectionComplete: { data in
print("Success! Connection ID: \(data.connectionId)")

// Handle success on main thread
DispatchQueue.main.async {
self.showAlert(title: "Success",
message: "Account connected successfully!")
}
},
onConnectionError: { error in
print("Connection error: \(error.error)")

// Handle error on main thread
DispatchQueue.main.async {
self.showAlert(title: "Error",
message: error.error)
}
}
)
}

} catch {
showAlert(title: "Error", message: error.localizedDescription)
}
}

private func getIntentToken() async throws -> String {
let url = URL(string: "https://your-api.com/api/intent-token")!
let (data, _) = try await URLSession.shared.data(from: url)

struct TokenResponse: Codable {
let intentToken: String
}

let response = try JSONDecoder().decode(TokenResponse.self, from: data)
return response.intentToken
}

private func showAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true)
}
}

Backend Implementation

Your backend needs to create intent tokens for the iOS app to use. When you create an intent token, you'll specify which resources you want access to. To see all the different integrations and their resources, visit the integrations explorer, see Here's a simple example:

Node.js Example

app.post('/api/intent-token', async (req, res) => {
try {
// 1. Get access token using OAuth 2.0 Client Credentials
const accessToken = await getPassageAccessToken();

// 2. Create intent token
const response = await fetch('https://api.getpassage.ai/intent-token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
integrationId: 'airbnb', // or other integrations
resources: {
trip: {
read: {}
}
}
})
});

const { intentToken } = await response.json();

res.json({ intentToken });

} catch (error) {
res.status(500).json({ error: error.message });
}
});

async function getPassageAccessToken() {
const response = await fetch('https://api.getpassage.ai/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: process.env.PASSAGE_CLIENT_ID,
client_secret: process.env.PASSAGE_CLIENT_SECRET
})
});

const { access_token } = await response.json();
return access_token;
}

API Reference

Configuration

public struct PassageConfig {
public init(
baseUrl: String? = nil, // Custom base URL (optional)
socketUrl: String? = nil, // Custom socket URL (optional)
socketNamespace: String? = nil, // Custom socket namespace (optional)
debug: Bool = false, // Enable debug logging
agentName: String? = nil // Custom agent name (optional)
)
}

Use defaults in production. Only override URLs for custom environments.

Core Methods

// Singleton instance
Passage.shared

// Configure SDK (call once at app startup)
func configure(_ config: PassageConfig)

// Open connection flow
func open(
token: String,
presentationStyle: PassagePresentationStyle = .modal,
from viewController: UIViewController? = nil,
onConnectionComplete: ((PassageSuccessData) -> Void)? = nil,
onConnectionError: ((PassageErrorData) -> Void)? = nil,
onDataComplete: ((PassageDataResult) -> Void)? = nil,
onExit: ((String?) -> Void)? = nil
)

// Options-based API
func open(_ options: PassageOpenOptions, from viewController: UIViewController? = nil)

// Close connection programmatically
func close()

Data Types

PassagePresentationStyle

public enum PassagePresentationStyle {
case modal // Page sheet modal (default)
case fullScreen // Full screen presentation
}

PassageSuccessData

public struct PassageSuccessData {
public let history: [PassageHistoryItem] // Captured data items
public let connectionId: String // Unique connection identifier
}

public struct PassageHistoryItem {
public let vendorId: String // Unique identifier for this item
public let structuredData: Any? // Integration-specific data (see Integration Data Types)
public let rawData: Any? // Original raw data from the service
public let enrichedData: Any? // Additional enriched data (where available)
}

📚 For detailed data structures returned by each integration, see Integration Data Types →

PassageErrorData

public struct PassageErrorData {
public let error: String // Error message
public let data: Any? // Additional error data
}

PassageDataResult

public struct PassageDataResult {
public let data: Any? // Raw data payload
public let prompts: [[String: Any]]? // Associated prompts
}

PassageOpenOptions

public struct PassageOpenOptions {
public let intentToken: String?
public let prompts: [PassagePrompt]?
public let onConnectionComplete: ((PassageSuccessData) -> Void)?
public let onConnectionError: ((PassageErrorData) -> Void)?
public let onDataComplete: ((PassageDataResult) -> Void)?
public let onExit: ((String?) -> Void)?
public let presentationStyle: PassagePresentationStyle?
}

Performance

The Passage SDK is designed to be lightweight with minimal impact on your app's performance.

Benchmarking Results

Performance measurements conducted on iPhone 15 Pro with iOS 17 using real-time monitoring during typical SDK usage:

Kroger.com (Complex E-commerce Website)

MetricBaselineWith Passage SDKOverheadMaximum Spikes
Memory9.2 MB22.8 MB+13.6 MB25.0 MB during page load
CPU0.0%0.0% avgMinimal18% during page load
App StartupN/AN/ANo impactN/A

Audible.com (Media/Content Website)

MetricBaselineWith Passage SDKOverheadMaximum Spikes
Memory8.7 MB23.4 MB+14.7 MB24.6 MB during page load
CPU0.0%0.0% avgMinimal7% during page load
App StartupN/AN/ANo impactN/A

Performance Characteristics

  • Memory Usage: ~14-15 MB overhead for full SDK functionality including WebView rendering
  • CPU Impact: Minimal sustained usage with brief spikes during web page loading operations
    • Complex e-commerce sites: Spikes up to 18% during heavy page loads
    • Media content sites: Spikes up to 7% during typical page operations
  • Web Page Complexity: Performance scales with website complexity and JavaScript usage
  • UI Responsiveness: Maintains smooth 120/60+ FPS during normal usage
  • Startup Time: No measurable impact on app launch performance
  • Memory Management: WebView instances are automatically released after sessions to free memory while preserving cookies/localStorage for user convenience

Integration Data Types

  • Browse integrations and resources in the explorer

Example App

The SDK includes a complete example app demonstrating all features:

  1. Open Example/PassageExample.xcodeproj in Xcode
  2. Add your backend endpoint URL in the configuration
  3. Build and run on device or simulator
  4. Tap "Connect with Passage" to test the integration

Support

If you encounter issues not covered here: