Circadify

Error Handling

Catch and recover from Circadify Python SDK errors across configuration, camera, capture, network, and cancellation paths.

The Python SDK raises CircadifyError for every expected failure — configuration, camera, capture, wire protocol, and cancellation. Wrap your measurement calls in try/except CircadifyError and branch on the subclass or on e.code.

Note

By default the SDK raises the real error and never fabricates vitals. Returning synthetic vitals is opt-in: pass allow_fallback_vitals=True to the constructor and only transient infrastructure failures (NetworkError, UploadFailedError, ScanTimeoutError, ServiceUnavailableError) return a placeholder VitalSigns with is_fallback=True and confidence=0.0 instead of raising. Configuration, camera, capture, quota, and inference (ProcessingFailedError) errors always raise.

The CircadifyError class

Every SDK exception derives from CircadifyError, so a single except CircadifyError catches all of them. Each instance carries a stable string code, a human-readable message, and retry metadata.

AttributeTypeDescription
codestrStable error identifier (e.g. "CAMERA_ERROR"). Branch on this instead of the class name.
messagestrHuman-readable description.
status`intNone`
retryableboolTrue if retrying the same call may succeed.
retry_after`floatNone`
from circadify import CircadifyClient, CircadifyError
 
client = CircadifyClient(api_key="ck_test_your_key_here")
try:
    result = client.measure_vitals()
except CircadifyError as e:
    print(e.code, e.message, e.status, e.retryable, e.retry_after)
python
Note

Codes are plain strings — there is no CircadifyErrorCode enum. Compare against the literal values in the tables below.

Basic handling

Catch the specific subclass when you want type-specific recovery, or fall back to a code switch and the retryable flag for everything else. Never hardcode a real API key — read it from the environment.

import os
from circadify import (
    CircadifyClient,
    CircadifyError,
    InvalidApiKeyError,
    CameraError,
    CaptureFailedError,
    QuotaExceededError,
    ScanCancelledError,
)
 
client = CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"])
 
try:
    result = client.measure_vitals()
    print(result.heart_rate, result.confidence)
except InvalidApiKeyError:
    print("Check your ck_ API key.")
except CameraError:
    print("No usable camera was found.")
except CaptureFailedError:
    print("Not enough clean frames — improve lighting and hold still.")
except QuotaExceededError:
    print("Account quota exhausted.")
except ScanCancelledError:
    pass  # user cancelled — nothing to do
except CircadifyError as e:
    if e.retryable:
        print("Transient failure, safe to retry:", e.message)
    else:
        print("Unrecoverable:", e.code, e.message)
finally:
    client.close()
python

Error reference

Every row maps an exception class to its code, whether it is retryable, and what triggered it.

Configuration

ExceptionCodeRetryableDescription
MissingApiKeyErrorMISSING_API_KEYNoNo API key was passed to CircadifyClient.
InvalidApiKeyErrorAPI_KEY_INVALIDNoThe key is malformed, revoked, or rejected (HTTP 401).

Dependencies

ExceptionCodeRetryableDescription
MissingDependencyErrorMISSING_DEPENDENCYNoAn optional extra is missing — install circadify[camera] for capture and the face-glow overlay.

Camera & capture

ExceptionCodeRetryableDescription
CameraErrorCAMERA_ERRORNoThe webcam could not be opened or read.
CaptureFailedErrorCAPTURE_FAILEDYesToo few usable frames or no face detected during capture.

Server & wire

ExceptionCodeRetryableDescription
ForbiddenErrorFORBIDDENNoThe key is valid but not permitted for this operation (HTTP 403).
QuotaExceededErrorQUOTA_EXCEEDEDNoAccount scan quota is exhausted (HTTP 429).
RateLimitedErrorRATE_LIMIT_EXCEEDEDYesRequest rate limit hit (HTTP 429); honor retry_after.
SessionNotFoundErrorSESSION_NOT_FOUNDNoThe referenced session does not exist (HTTP 404).
SessionExpiredErrorSESSION_EXPIREDNoThe session expired before completion (HTTP 410).
ProcessingFailedErrorPROCESSING_FAILEDYesServer-side inference failed (HTTP 422).
UploadFailedErrorUPLOAD_FAILEDYesThe tensor upload did not complete.
NetworkErrorNETWORK_ERRORYesThe request never reached the server.
ServiceUnavailableErrorSERVICE_UNAVAILABLEYesThe server returned a 5xx response.

Scan lifecycle

ExceptionCodeRetryableDescription
ScanTimeoutErrorTIMEOUTYesResult polling exceeded poll_timeout. (Capture/readiness timeouts surface as CaptureFailedError.)
ScanCancelledErrorCANCELLEDNoThe scan was cancelled via cancel() or a cancel_event.

Retry with backoff

Retry only when e.retryable is True. Prefer the server-provided e.retry_after when present; otherwise back off exponentially.

import time
from circadify import CircadifyClient, CircadifyError
 
def measure_with_retry(client: CircadifyClient, max_retries: int = 3):
    for attempt in range(max_retries):
        try:
            return client.measure_vitals()
        except CircadifyError as e:
            if not e.retryable or attempt == max_retries - 1:
                raise
            delay = e.retry_after if e.retry_after is not None else 2 ** attempt
            time.sleep(delay)
    raise RuntimeError("unreachable")
python
Caution

Never auto-retry API-key, quota, or permission errors (MISSING_API_KEY, API_KEY_INVALID, FORBIDDEN, QUOTA_EXCEEDED). Their retryable flag is False because they require a configuration or account change, not another attempt — looping on them just burns time and noise.

Next Steps