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.
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)
}swiftEvery 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)")
}swiftError Reference
Camera Errors
| Error | Description | Retryable |
|---|---|---|
cameraNotAvailable | No camera on this device | No |
cameraPermissionDenied | User denied camera access | No |
cameraInUse | Camera is in use by another app | No |
Configuration Errors
| Error | Description | Retryable |
|---|---|---|
missingApiKey | No API key provided | No |
invalidApiKey | Key is malformed, revoked, or expired | No |
Capture Errors
| Error | Description | Retryable |
|---|---|---|
captureFailed(String) | Frame capture failed | No |
qualityTooLow | Capture quality too poor for analysis | No |
faceNotDetected | No face found in camera feed | No |
Network Errors
| Error | Description | Retryable |
|---|---|---|
networkError(String) | Network request failed | Yes |
uploadFailed(String) | Secure upload failed | Yes |
apiError(String) | API returned an error | No |
Session Errors
| Error | Description | Retryable |
|---|---|---|
sessionExpired | Session timed out | No |
sessionNotFound | Session ID doesn't exist | No |
Processing Errors
| Error | Description | Retryable |
|---|---|---|
processingFailed(String) | Processing failed | No |
timeout | Polling for results timed out | Yes |
Usage Errors
| Error | Description | Retryable |
|---|---|---|
quotaExceeded | Monthly scan quota reached | No |
rateLimited(retryAfter:) | Request rate limit exceeded (sandbox). retryAfter is the SDK's suggested seconds to wait. | Yes |
General
| Error | Description | Retryable |
|---|---|---|
cancelled | Measurement cancelled via cancel() | No |
unknown(String) | Unexpected error | No |
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")
}swiftNever retry cameraPermissionDenied, quotaExceeded, or missingApiKey. These require user or configuration changes before they can succeed.
Next Steps
- Methods — Full method reference
- Camera & Permissions — Handle permission denial
- REST API Errors — Server-side error reference