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
Installation
Requirements
- iOS 15.1+
- Swift 5+
- Xcode 13+
Swift Package Manager (Recommended)
Add the package in Xcode:
- File → Add Package Dependencies
- Enter repository URL:
https://github.com/tailriskai/passage-swift.git - 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:
- Configure SDK → Initialize once in your app
- Backend Setup → Create intent token endpoint
- Get Token → Call your backend for intent token
- Open Passage → Launch connection flow with callbacks
- 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)
| Metric | Baseline | With Passage SDK | Overhead | Maximum Spikes |
|---|---|---|---|---|
| Memory | 9.2 MB | 22.8 MB | +13.6 MB | 25.0 MB during page load |
| CPU | 0.0% | 0.0% avg | Minimal | 18% during page load |
| App Startup | N/A | N/A | No impact | N/A |
Audible.com (Media/Content Website)
| Metric | Baseline | With Passage SDK | Overhead | Maximum Spikes |
|---|---|---|---|---|
| Memory | 8.7 MB | 23.4 MB | +14.7 MB | 24.6 MB during page load |
| CPU | 0.0% | 0.0% avg | Minimal | 7% during page load |
| App Startup | N/A | N/A | No impact | N/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:
- Open
Example/PassageExample.xcodeprojin Xcode - Add your backend endpoint URL in the configuration
- Build and run on device or simulator
- Tap "Connect with Passage" to test the integration
Support
If you encounter issues not covered here:
- GitHub Issues: Create an issue with logs and steps to reproduce
- Email Support: support@getpassage.ai
- Documentation: Latest updates at docs.getpassage.ai