iOS SDK
The AgentKey iOS SDK provides native iOS integration for seamless authentication flows with a modern, Plaid-like provider selection interface. It handles authentication, token management, and connection status for iOS applications.
Installation
Swift Package Manager (Recommended)
Add the iOS SDK to your Xcode project using Swift Package Manager:
- In Xcode, go to File > Add Package Dependencies
- Enter the repository URL:
https://github.com/yourusername/agentkey-ios-sdk
- Select the version and add to your target
Manual Installation
- Download the
AgentKey.framework
from the releases page
- Drag and drop it into your Xcode project
- Make sure to add it to your target’s “Frameworks, Libraries, and Embedded Content”
Quick Start
Basic Setup
import AgentKey
class ViewController: UIViewController {
private var agentKey: AKAgentKey?
override func viewDidLoad() {
super.viewDidLoad()
setupAgentKey()
}
private func setupAgentKey() {
let configuration = AKConfiguration(
linkToken: "link_token_from_your_backend",
clientID: "your_client_id",
clientSecret: "your_client_secret"
)
agentKey = AKAgentKey(configuration: configuration)
agentKey?.delegate = self
}
@IBAction func connectAccountTapped(_ sender: UIButton) {
agentKey?.present(from: self)
}
}
// MARK: - AgentKey Delegate
extension ViewController: AKAgentKeyDelegate {
func agentKey(_ agentKey: AKAgentKey, didCompleteWith result: AKConnectionResult) {
switch result {
case .success(let publicToken, let metadata):
print("✅ Connected! Public Token: \(publicToken)")
// Send publicToken to your backend for exchange
case .failure(let error):
print("❌ Connection failed: \(error.localizedDescription)")
case .cancelled(let metadata):
print("ℹ️ User cancelled connection")
}
}
func agentKey(_ agentKey: AKAgentKey, didUpdateStatus status: String) {
print("Status: \(status)")
}
func agentKeyDidRequestDismissal(_ agentKey: AKAgentKey) {
// Handle any cleanup if needed
}
}
Complete Integration with SwiftUI
import SwiftUI
import AgentKey
struct ContentView: View {
@State private var publicToken: String?
@State private var connectionStatus: String = ""
@State private var isLoading: Bool = false
@State private var agentKey: AKAgentKey?
var body: some View {
VStack(spacing: 20) {
Text("Connect Your Account")
.font(.title)
.fontWeight(.bold)
if let token = publicToken {
VStack {
Text("✅ Connected Successfully!")
.foregroundColor(.green)
.font(.headline)
Text("Public Token:")
.font(.caption)
.foregroundColor(.secondary)
Text(String(token.prefix(40)) + "...")
.font(.monospaced(.caption)())
.padding()
.background(Color.gray.opacity(0.1))
.cornerRadius(8)
}
.padding()
.background(Color.green.opacity(0.1))
.cornerRadius(12)
}
Button(action: {
presentAgentKey()
}) {
HStack {
if isLoading {
ProgressView()
.scaleEffect(0.8)
}
Text(publicToken != nil ? "Connect Another Account" : "Connect Account")
}
.foregroundColor(.white)
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
.disabled(isLoading)
if !connectionStatus.isEmpty {
Text(connectionStatus)
.font(.caption)
.foregroundColor(.secondary)
.padding()
}
}
.padding()
.onAppear {
setupAgentKey()
}
}
private func setupAgentKey() {
let configuration = AKConfiguration(
linkToken: "your_link_token_here",
clientID: "your_client_id",
clientSecret: "your_client_secret",
environment: .sandbox
)
agentKey = AKAgentKey(configuration: configuration)
agentKey?.delegate = AgentKeyCoordinator(
onSuccess: { publicToken, metadata in
self.publicToken = publicToken
self.isLoading = false
self.connectionStatus = "Account connected successfully!"
},
onFailure: { error in
self.isLoading = false
self.connectionStatus = "Connection failed: \(error.localizedDescription)"
},
onStatusUpdate: { status in
self.connectionStatus = status
}
)
}
private func presentAgentKey() {
guard let agentKey = agentKey else { return }
isLoading = true
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootViewController = windowScene.windows.first?.rootViewController {
agentKey.present(from: rootViewController)
}
}
}
// Helper class to bridge delegate pattern with SwiftUI
class AgentKeyCoordinator: NSObject, AKAgentKeyDelegate {
let onSuccess: (String, AKConnectionMetadata) -> Void
let onFailure: (AKError) -> Void
let onStatusUpdate: (String) -> Void
init(onSuccess: @escaping (String, AKConnectionMetadata) -> Void,
onFailure: @escaping (AKError) -> Void,
onStatusUpdate: @escaping (String) -> Void) {
self.onSuccess = onSuccess
self.onFailure = onFailure
self.onStatusUpdate = onStatusUpdate
}
func agentKey(_ agentKey: AKAgentKey, didCompleteWith result: AKConnectionResult) {
switch result {
case .success(let publicToken, let metadata):
onSuccess(publicToken, metadata)
case .failure(let error):
onFailure(error)
case .cancelled:
onFailure(AKError.authenticationFailed("User cancelled"))
}
}
func agentKey(_ agentKey: AKAgentKey, didUpdateStatus status: String) {
onStatusUpdate(status)
}
func agentKeyDidRequestDismissal(_ agentKey: AKAgentKey) {
// Handle dismissal if needed
}
}
API Reference
AKAgentKey Class
The main class for handling AgentKey authentication flows.
Initialization
public init(configuration: AKConfiguration)
Methods
// Present the authentication flow
public func present(from presentingViewController: UIViewController)
// Factory method for quick setup
public static func create(
linkToken: String,
clientID: String,
clientSecret: String,
environment: AKEnvironment = .sandbox,
backendURL: URL? = nil
) -> AKAgentKey
AKConfiguration
Configuration object for initializing AgentKey.
public struct AKConfiguration {
public let linkToken: String // Link token from your backend
public let clientID: String // Your client ID
public let clientSecret: String // Your client secret
public let backendURL: URL? // Custom backend URL (optional)
public let environment: AKEnvironment // Environment setting
public init(
linkToken: String,
clientID: String,
clientSecret: String,
backendURL: URL? = nil,
environment: AKEnvironment = .sandbox
)
}
AKAgentKeyDelegate Protocol
Delegate methods for handling authentication events.
public protocol AKAgentKeyDelegate: AnyObject {
func agentKey(_ agentKey: AKAgentKey, didCompleteWith result: AKConnectionResult)
func agentKey(_ agentKey: AKAgentKey, didUpdateStatus status: String)
func agentKeyDidRequestDismissal(_ agentKey: AKAgentKey)
}
Types
// Environment options
public enum AKEnvironment: String {
case development = "development"
case sandbox = "sandbox"
case production = "production"
}
// Connection result
public enum AKConnectionResult {
case success(String, AKConnectionMetadata) // publicToken, metadata
case failure(AKError) // error details
case cancelled(AKConnectionMetadata?) // user cancelled
}
// Connection metadata
public struct AKConnectionMetadata {
public let sessionID: String?
public let providerName: String?
public let timestamp: Date?
public let requestID: String?
public let accounts: [AKAccount]
public let connectedProviders: [String]
}
// Account information
public struct AKAccount {
public let id: String
public let name: String
public let type: String
public let providerName: String
public let isActive: Bool
}
// Error types
public enum AKError: Error {
case invalidToken(String)
case tokenExpired
case authenticationFailed(String)
case networkError(Error)
case initializationFailed(String)
case sessionTimeout
case invalidBackendURL
case invalidResponse
case noData
case serverError(String)
case unknownError(String)
}
Features
Provider Selection Interface
The iOS SDK includes a modern, Plaid-like provider selection screen featuring:
- Clean Card Layout: Provider logos organized in a clean grid
- Search Functionality: Search through providers by name or category
- Organized Categories:
- Utilities (PG&E, ConEd, LA DWP, SoCalGas)
- Streaming (Netflix, Hulu, Disney+, Amazon Prime)
- Internet & Cable (Comcast Xfinity, Spectrum)
- Phone & Mobile (Verizon, AT&T)
Link Token Management
Create link tokens from your backend:
AKAgentKey.createLinkToken(
AKLinkTokenRequest(clientUserID: "user_123"),
clientID: "your_client_id",
clientSecret: "your_client_secret"
) { result in
switch result {
case .success(let response):
// Use response.linkToken to initialize AgentKey
let config = AKConfiguration(
linkToken: response.linkToken,
clientID: "your_client_id",
clientSecret: "your_client_secret"
)
let agentKey = AKAgentKey(configuration: config)
case .failure(let error):
print("Failed to create link token: \(error)")
}
}
Authentication Flow
The iOS SDK follows this authentication pattern:
- Get Link Token: Fetch a link token from your backend
- Configure SDK: Initialize AKAgentKey with the link token
- Present UI: User selects provider and authenticates
- Receive Public Token: Your delegate receives a public token
- Send to Backend: Exchange the public token on your backend
Error Handling
Handle different types of errors in your delegate:
func agentKey(_ agentKey: AKAgentKey, didCompleteWith result: AKConnectionResult) {
switch result {
case .success(let publicToken, let metadata):
// Handle successful connection
handleSuccessfulConnection(publicToken: publicToken, metadata: metadata)
case .failure(let error):
switch error {
case .invalidToken(let message):
showAlert(title: "Invalid Token", message: message)
case .tokenExpired:
showAlert(title: "Token Expired", message: "Please try again")
case .authenticationFailed(let message):
showAlert(title: "Authentication Failed", message: message)
case .networkError(let underlyingError):
showAlert(title: "Network Error", message: underlyingError.localizedDescription)
case .sessionTimeout:
showAlert(title: "Session Timeout", message: "Please try again")
default:
showAlert(title: "Error", message: error.localizedDescription)
}
case .cancelled(let metadata):
print("User cancelled authentication")
// Optionally handle cancellation
}
}
Best Practices
Link Token Management
Always fetch link tokens from your secure backend:
// ✅ Good - Fetch from your secure backend
func fetchLinkToken() async throws -> String {
let url = URL(string: "https://your-backend.com/api/link-token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestBody = ["clientUserId": "user_123"]
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody)
let (data, _) = try await URLSession.shared.data(for: request)
let response = try JSONSerialization.jsonObject(with: data) as! [String: Any]
return response["linkToken"] as! String
}
Token Exchange
Send public tokens to your backend immediately:
func agentKey(_ agentKey: AKAgentKey, didCompleteWith result: AKConnectionResult) {
switch result {
case .success(let publicToken, _):
// ✅ Good - Immediately send to backend
Task {
await exchangePublicToken(publicToken)
}
default:
break
}
}
func exchangePublicToken(_ publicToken: String) async {
// Send to your backend for secure exchange
let url = URL(string: "https://your-backend.com/api/exchange-token")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let requestBody = ["publicToken": publicToken]
request.httpBody = try? JSONSerialization.data(withJSONObject: requestBody)
do {
let (_, _) = try await URLSession.shared.data(for: request)
print("Token exchanged successfully")
} catch {
print("Token exchange failed: \(error)")
}
}
User Experience
Provide clear feedback and loading states:
class ConnectionViewController: UIViewController {
@IBOutlet weak var connectButton: UIButton!
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
func agentKey(_ agentKey: AKAgentKey, didUpdateStatus status: String) {
DispatchQueue.main.async {
self.statusLabel.text = status
}
}
@IBAction func connectAccountTapped(_ sender: UIButton) {
connectButton.isEnabled = false
activityIndicator.startAnimating()
statusLabel.text = "Initializing connection..."
agentKey?.present(from: self)
}
func handleConnectionComplete() {
DispatchQueue.main.async {
self.connectButton.isEnabled = true
self.activityIndicator.stopAnimating()
self.statusLabel.text = "Ready to connect"
}
}
}
Environment Configuration
The SDK automatically infers the environment from your client secret prefix:
ak_dev_*
→ Development (http://localhost:4000
)
ak_sandbox_*
→ Sandbox (https://sandbox-api.agentkey.com
)
ak_prod_*
→ Production (https://api.agentkey.com
)
You can also explicitly set the environment:
let configuration = AKConfiguration(
linkToken: linkToken,
clientID: clientID,
clientSecret: clientSecret,
environment: .production // Explicit environment
)
Next Steps
- Backend Integration - Set up your backend to create link tokens and exchange public tokens
- Examples - See complete iOS integration examples
- API Reference - Backend API documentation