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:

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

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

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

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

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

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

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:

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

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

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:

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

// 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

Last updated

Was this helpful?