# Swift 🦉

The official Swift client for the CardScan API provides a native Swift interface with async/await support.

## Installation

### Swift Package Manager

Add the following to your `Package.swift`:

```swift
dependencies: [
    .package(url: "https://github.com/CardScan-ai/api-clients.git", from: "1.0.0")
]
```

Or in Xcode:

1. File → Add Package Dependencies
2. Enter: `https://github.com/CardScan-ai/api-clients.git`
3. Select version rule and add to your project

## Basic Usage

```swift
import CardScanClient

// Initialize with your API key
let apiKey = "sk_test_cardscan_ai_..."
let client = CardScanAPI(apiKey: apiKey)

// Generate a session token for a user
do {
    let tokenResponse = try await client.getAccessToken(userId: "unique-user-123")
    let sessionToken = tokenResponse.Token
    let identityId = tokenResponse.IdentityId
    let sessionId = tokenResponse.session_id
    
    // Initialize client with session token for frontend operations
    let userClient = CardScanAPI(sessionToken: sessionToken, live: false)
} catch {
    print("Error creating access token: \(error)")
}
```

## Card Scanning Workflow

### 1. Create a Card

```swift
struct CardCreationExample {
    let client: CardScanAPI
    
    func createCard() async throws -> CardApiResponse {
        let request = CreateCardRequest(
            enableBacksideScan: false,
            enableLivescan: false,
            metadata: [
                "patient_id": "12345",
                "visit_id": "v-67890"
            ]
        )
        
        let card = try await client.createCard(request: request)
        print("Card ID: \(card.cardId)")
        print("State: \(card.state)") // .pending
        
        return card
    }
}
```

### 2. Generate Upload URL

```swift
func generateUploadUrl(for cardId: String) async throws -> GenerateCardUploadUrlResponse {
    let request = GenerateCardUploadUrlRequest(
        orientation: .front,
        captureType: .manual
    )
    
    let uploadData = try await client.generateCardUploadUrl(
        cardId: cardId,
        request: request
    )
    
    return uploadData
}
```

### 3. Upload Image

```swift
import Foundation

func uploadImage(_ image: UIImage, using uploadData: GenerateCardUploadUrlResponse) async throws {
    guard let imageData = image.jpegData(compressionQuality: 0.8) else {
        throw CardScanError.invalidImage
    }
    
    var request = URLRequest(url: uploadData.uploadUrl)
    request.httpMethod = "POST"
    
    // Create multipart form data
    let boundary = UUID().uuidString
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
    var body = Data()
    
    // Add upload parameters
    for (key, value) in uploadData.uploadParameters {
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
        body.append("\(value)\r\n".data(using: .utf8)!)
    }
    
    // Add image data
    body.append("--\(boundary)\r\n".data(using: .utf8)!)
    body.append("Content-Disposition: form-data; name=\"file\"; filename=\"card.jpg\"\r\n".data(using: .utf8)!)
    body.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
    body.append(imageData)
    body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
    
    request.httpBody = body
    
    let (_, response) = try await URLSession.shared.data(for: request)
    
    guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else {
        throw CardScanError.uploadFailed
    }
}
```

### 4. Poll for Results

```swift
func waitForCompletion(cardId: String, timeout: TimeInterval = 300) async throws -> CardApiResponse {
    let startTime = Date()
    
    while Date().timeIntervalSince(startTime) < timeout {
        let card = try await client.getCard(cardId: cardId)
        
        switch card.state {
        case .completed, .error:
            return card
        default:
            // Wait 2 seconds before polling again
            try await Task.sleep(nanoseconds: 2_000_000_000)
        }
    }
    
    throw CardScanError.timeout
}

// Usage
let completedCard = try await waitForCompletion(cardId: card.cardId)
if let details = completedCard.details {
    print("Member ID: \(details.memberId ?? "N/A")")
    print("Group Number: \(details.groupNumber ?? "N/A")")
}
```

## Error Handling

```swift
enum CardScanError: Error {
    case invalidImage
    case uploadFailed
    case timeout
}

do {
    let card = try await client.getCard(cardId: "invalid-id")
} catch let error as ApiError {
    switch error.statusCode {
    case 401:
        print("Invalid API key or session token")
    case 404:
        print("Card not found")
    case 429:
        print("Rate limit exceeded")
    default:
        print("API Error: \(error.statusCode) - \(error.message)")
    }
} catch {
    print("Unexpected error: \(error)")
}
```

## WebSocket Support

For real-time updates:

```swift
import Foundation

class CardScanWebSocket: NSObject {
    private var webSocket: URLSessionWebSocketTask?
    private let session = URLSession(configuration: .default)
    private let token: String
    
    init(token: String) {
        self.token = token
        super.init()
    }
    
    func connect() {
        let url = URL(string: "wss://sandbox.cardscan.ai/v1/ws?token=\(token)")!
        webSocket = session.webSocketTask(with: url)
        webSocket?.resume()
        receiveMessage()
    }
    
    private func receiveMessage() {
        webSocket?.receive { [weak self] result in
            switch result {
            case .success(let message):
                switch message {
                case .string(let text):
                    self?.handleMessage(text)
                default:
                    break
                }
                self?.receiveMessage()
            case .failure(let error):
                print("WebSocket error: \(error)")
            }
        }
    }
    
    private func handleMessage(_ text: String) {
        guard let data = text.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let type = json["type"] as? String else { return }
        
        switch type {
        case "card.processing":
            print("Card processing started")
        case "card.completed":
            if let cardId = json["card_id"] as? String {
                print("Card completed: \(cardId)")
            }
        case "card.error":
            if let error = json["error"] as? String {
                print("Card error: \(error)")
            }
        default:
            break
        }
    }
}
```

## Eligibility Verification

```swift
func verifyEligibility(for card: CardApiResponse) async throws -> EligibilityApiResponse {
    let request = CreateEligibilityRequest(
        eligibility: EligibilityRequest(
            provider: ProviderDto(
                firstName: "John",
                lastName: "Smith",
                npi: "1234567890"
            ),
            subscriber: SubscriberDto(
                firstName: card.details?.memberName ?? "",
                lastName: card.details?.memberName ?? "",
                dateOfBirth: "1980-01-01"
            )
        )
    )
    
    let eligibility = try await client.createEligibility(
        cardId: card.cardId,
        request: request
    )
    
    // Poll for results
    return try await waitForEligibilityCompletion(
        eligibilityId: eligibility.eligibilityId
    )
}
```

## SwiftUI Integration

```swift
import SwiftUI
import CardScanClient

struct CardScannerView: View {
    @State private var isScanning = false
    @State private var scannedCard: CardApiResponse?
    @State private var error: Error?
    
    let client: CardScanAPI
    
    var body: some View {
        VStack {
            if let card = scannedCard {
                CardDetailsView(card: card)
            } else {
                Button("Scan Insurance Card") {
                    Task {
                        await scanCard()
                    }
                }
                .disabled(isScanning)
            }
            
            if isScanning {
                ProgressView("Processing...")
            }
            
            if let error = error {
                Text("Error: \(error.localizedDescription)")
                    .foregroundColor(.red)
            }
        }
        .padding()
    }
    
    @MainActor
    private func scanCard() async {
        isScanning = true
        error = nil
        
        do {
            // Create card
            let card = try await client.createCard(
                request: CreateCardRequest(enableBacksideScan: false)
            )
            
            // In a real app, capture/select image here
            // For demo, assume we have an image
            
            // Process and wait for results
            scannedCard = try await waitForCompletion(cardId: card.cardId)
        } catch {
            self.error = error
        }
        
        isScanning = false
    }
}
```

## Combine Support

For reactive programming with Combine:

```swift
import Combine

extension CardScanAPI {
    func createCardPublisher(request: CreateCardRequest) -> AnyPublisher<CardApiResponse, Error> {
        Future { promise in
            Task {
                do {
                    let card = try await self.createCard(request: request)
                    promise(.success(card))
                } catch {
                    promise(.failure(error))
                }
            }
        }
        .eraseToAnyPublisher()
    }
}

// Usage
let cancellable = client.createCardPublisher(request: request)
    .sink(
        receiveCompletion: { completion in
            if case .failure(let error) = completion {
                print("Error: \(error)")
            }
        },
        receiveValue: { card in
            print("Created card: \(card.cardId)")
        }
    )
```

## Configuration

```swift
// Custom configuration
let configuration = CardScanConfiguration(
    baseURL: "https://sandbox.cardscan.ai/v1",
    timeout: 30,
    retryCount: 3,
    retryDelay: 1.0
)

let client = CardScanAPI(
    apiKey: "sk_test_cardscan_ai_...",
    configuration: configuration
)
```

## Source Code

View the source code and contribute: [GitHub](https://github.com/CardScan-ai/api-clients/tree/main/clients/cardscan-swift)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cardscan.ai/api-clients/swift.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
