Circadify

Error Handling

Handle all Circadify iOS SDK error cases — camera, capture, network, session, and usage errors.

All errors from measureVitals(camera: camera) are thrown as CircadifyError values. Use Swift's do/catch with pattern matching to handle specific cases.

Note

The SDK never returns fabricated or estimated vitals. On any failure it throws a CircadifyError instead of returning a result — there is no offline fallback or guessed measurement. Surface the error and let the user retry under better conditions (better lighting, a steadier hold, a stronger connection).

Error Format

public enum CircadifyError: LocalizedError, Equatable {
    // Camera
    case cameraNotAvailable
    case cameraPermissionDenied
    case cameraInUse
 
    // Configuration
    case invalidApiKey
    case missingApiKey
 
    // Capture
    case captureFailed(String)
    case qualityTooLow
    case faceNotDetected
 
    // Network
    case networkError(String)
    case uploadFailed(String)
    case apiError(String)
 
    // Session
    case sessionExpired
    case sessionNotFound
 
    // Processing
    case processingFailed(String)
    case timeout
 
    // Usage
    case quotaExceeded
    case rateLimited(retryAfter: Int)
 
    // General
    case cancelled
    case unknown(String)
}
swift

Every case has a human-readable errorDescription and an isRetryable flag.

Handling Errors

do {
    let result = try await sdk.measureVitals(camera: camera)
    displayResults(result)
} catch let error as CircadifyError {
    switch error {
    case .cameraPermissionDenied:
        showSettingsPrompt("Camera access is required. Enable it in Settings.")
 
    case .cameraNotAvailable:
        showAlert("No camera found on this device.")
 
    case .faceNotDetected:
        showAlert("No face detected. Make sure your face is visible and well-lit.")
 
    case .qualityTooLow:
        showAlert("Scan quality was too low. Try again with better lighting.")
 
    case .rateLimited(let retryAfter):
        showAlert("Too many requests. Try again in \(retryAfter) seconds.")
 
    case .cancelled:
        break // User cancelled — no action needed
 
    default:
        if error.isRetryable {
            showRetryPrompt(error.errorDescription ?? "Something went wrong.")
        } else {
            showAlert(error.errorDescription ?? "An error occurred.")
        }
    }
} catch {
    showAlert("Unexpected error: \(error.localizedDescription)")
}
swift

Error Reference

Camera Errors

ErrorDescriptionRetryable
cameraNotAvailableNo camera on this deviceNo
cameraPermissionDeniedUser denied camera accessNo
cameraInUseCamera is in use by another appNo

Configuration Errors

ErrorDescriptionRetryable
missingApiKeyNo API key providedNo
invalidApiKeyKey is malformed, revoked, or expiredNo

Capture Errors

ErrorDescriptionRetryable
captureFailed(String)Frame capture failedNo
qualityTooLowCapture quality too poor for analysisNo
faceNotDetectedNo face found in camera feedNo

Network Errors

ErrorDescriptionRetryable
networkError(String)Network request failedYes
uploadFailed(String)Secure upload failedYes
apiError(String)API returned an errorNo

Session Errors

ErrorDescriptionRetryable
sessionExpiredSession timed outNo
sessionNotFoundSession ID doesn't existNo

Processing Errors

ErrorDescriptionRetryable
processingFailed(String)Processing failedNo
timeoutPolling for results timed outYes

Usage Errors

ErrorDescriptionRetryable
quotaExceededMonthly scan quota reachedNo
rateLimited(retryAfter:)Request rate limit exceeded (sandbox). retryAfter is the SDK's suggested seconds to wait.Yes

General

ErrorDescriptionRetryable
cancelledMeasurement cancelled via cancel()No
unknown(String)Unexpected errorNo

Retry with Backoff

For retryable errors, wait before retrying:

func measureWithRetry(
    sdk: CircadifySDK,
    camera: CircadifyCamera,
    maxRetries: Int = 3
) async throws -> VitalSignsResult {
    for attempt in 0..<maxRetries {
        do {
            return try await sdk.measureVitals(camera: camera)
        } catch let error as CircadifyError where error.isRetryable {
            if attempt == maxRetries - 1 { throw error }
 
            let delay: UInt64
            if case .rateLimited(let retryAfter) = error {
                delay = UInt64(retryAfter) * 1_000_000_000
            } else {
                delay = UInt64(pow(2.0, Double(attempt))) * 1_000_000_000
            }
 
            try await Task.sleep(nanoseconds: delay)
        }
    }
 
    throw CircadifyError.unknown("Max retries exceeded")
}
swift
Caution

Never retry cameraPermissionDenied, quotaExceeded, or missingApiKey. These require user or configuration changes before they can succeed.

Next Steps