REST Client
Drive the raw REST session lifecycle from Python using CircadifyClient's low-level methods for headless, server, and batch workloads.
Most integrations call measure_vitals, measure_from_video, or measure_from_frames and let the SDK run the whole flow. This page is the escape hatch: drive the raw REST session lifecycle yourself when you have no camera, run on a server, batch many payloads, or supply your own transport. These methods live directly on CircadifyClient — there is no separate RestClient class — and they work on the core install with no [camera] extra.
When to use
Reach for the low-level path when the convenience methods do not fit:
- Headless servers and batch jobs — process pre-encoded tensors with no webcam attached.
- Custom transport or scheduling — manage the upload, retries, or polling cadence yourself.
- Owning the session yourself — interleave the four REST calls with your own pipeline.
The full wire protocol — including the free sandbox — runs on pip install circadify (deps: requests + numpy). You only need pip install "circadify[camera]" for the capture methods (measure_vitals / measure_from_video / measure_from_frames) and the circadify.ui window.
The low-level methods
scan_tensor is the one-call path: hand it an encoded tensor and it runs start → upload → complete → poll for you, returning a VitalSigns.
import os
from circadify import CircadifyClient
from circadify.tensor import placeholder_tensor
client = CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"])
result = client.scan_tensor(placeholder_tensor())
print(result.heart_rate, result.confidence)pythonIf you need to own each step, the four raw calls map one-to-one onto the REST endpoints:
| Method | REST endpoint | Returns |
|---|---|---|
start_session(demographics=None) | POST /sdk/session/start | Session (carries session_id and the presigned upload_url) |
upload(upload_url, data) | PUT to the presigned upload_url | None |
complete_upload(session_id) | POST /sdk/session/upload-complete | Optional[VitalSigns] |
poll_result(session_id, cancel_event=None) | GET /sdk/session/{id}/result | VitalSigns |
complete_upload may return a VitalSigns immediately when the result is ready; otherwise it returns None and you call poll_result to block until processing finishes. Pass a threading.Event as cancel_event to abort a long poll. Catch CircadifyError around all of these — see Error Handling.
Preparing the payload
The upload payload is the SDK-prepared 12-channel tensor, not raw video — raw video never leaves the device. Build it with circadify.tensor, whose byte layout (a 36-byte little-endian header plus a 12-channel 72×72 planar-RGB body, optionally gzipped) is byte-exact across SDK versions.
| Function | Purpose |
|---|---|
encode_tensor(...) | Serialize prepared frames into the byte-exact upload payload. |
decode_tensor(...) | Round-trip a payload back into its array form. |
pack_frame(...) | Pack a single frame into the planar-RGB tensor layout. |
placeholder_tensor(frame_count=1) | Build a throwaway payload for sandbox testing (pass a larger frame_count, e.g. 150, to mimic a real capture). |
placeholder_tensor produces a valid payload with no camera or GPU. The sandbox ignores tensor content, so it is the fastest way to exercise the full wire flow end to end against a ck_test_ key.
Full lifecycle example
This drives all four raw calls explicitly. The same flow is what scan_tensor runs for you.
-
Build and encode the tensor. Use
placeholder_tensorfor sandbox, orencode_tensorover your own prepared frames in production.
pythonimport os from circadify import CircadifyClient from circadify.tensor import placeholder_tensor client = CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"]) tensor_bytes = placeholder_tensor() -
Start the session.
start_sessionreturns aSessioncarrying thesession_idand the presignedupload_url.
pythonsession = client.start_session() -
Upload the payload.
PUTthe tensor bytes to the presigned URL.
pythonclient.upload(session.upload_url, tensor_bytes) -
Complete the upload. This may return a
VitalSignsimmediately, orNoneif processing is still running.
pythonresult = client.complete_upload(session.session_id) -
Poll for the result. If
complete_uploadreturnedNone, block until processing finishes.
pythonif result is None: result = client.poll_result(session.session_id) print(result.heart_rate, result.confidence) client.close()
CircadifyClient is a context manager, so you can let with close it for you instead of calling client.close():
import os
from circadify import CircadifyClient
from circadify.tensor import placeholder_tensor
with CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"]) as client:
result = client.scan_tensor(placeholder_tensor())
print(result.to_dict())pythonSandbox
A ck_test_ key plus placeholder_tensor() returns deterministic synthetic vitals with no camera and no GPU. It is free, zero quota, and ideal for CI or wiring up your transport before you have real capture in place. client.key_environment reports "test" or "live" so you can assert which path you are on.
import os
from circadify import CircadifyClient
from circadify.tensor import placeholder_tensor
client = CircadifyClient(api_key="ck_test_your_key_here") # never hardcode in real code
assert client.key_environment == "test"
result = client.scan_tensor(placeholder_tensor())
print(result.is_fallback, result.heart_rate)pythonAuthentication
Authenticate every call with your ck_* API key, sent as the X-API-Key header (the SDK adds it for you). Read it from the environment rather than hardcoding it.
export CIRCADIFY_API_KEY="ck_live_your_key_here"bashimport os
from circadify import CircadifyClient
client = CircadifyClient(api_key=os.environ["CIRCADIFY_API_KEY"])pythonA malformed key raises InvalidApiKeyError; an empty key raises MissingApiKeyError. After any call, client.last_rate_limit holds a RateLimitInfo or None. See Authentication for key types and rotation.
POST /sdk/session/start consumes 1 scan of quota per call on a live (ck_live_) key — start_session and scan_tensor both hit it. Never loop these to probe the API. Use a ck_test_ key for any testing: the sandbox is free and zero quota.
Next Steps
- REST API Overview — the wire protocol these methods speak.
- Sessions — the four endpoints in full HTTP detail.
- Methods — the higher-level capture methods most integrations use.
- Error Handling — catch
CircadifyErrorand read.code/.retryable.