AnyBio

Device ingest

The cellular device webhook (shared secret, per project), image ingest, waveform captures, and the provenance fields every observation carries - measured time versus received time.

Readings reach a program by four paths: a cellular device pushes each measurement to a per-project webhook; a phone or app posts an image or a waveform capture; the mobile SDK streams raw packets; a wearable account syncs on a schedule. This page covers the first two and the provenance fields all of them write. The SDK paths are documented under Stream on iOS and wearable connections under Connect devices.

The cellular device webhook

Cellular cuffs, scales, pulse oximeters, glucose meters and thermometers deliver each reading as one HTTP POST from the device supplier's hub to a webhook addressed to your project. There is no pull step: the value is in the payload, and the receiver resolves the patient and writes the observation directly.

POST https://api.anybio.io/webhooks/<provider>/{project_key}
Authorization: <the exact value you configured>
Content-Type: application/json

The provider segment and the full URL are shown on the dashboard's Integrations page once the source is added; copy it from there. The route is public (no API key) and authenticates by a shared secret.

Authentication

  • The hub sends a fixed Authorization header on every POST. You configure the expected value per project when you add the source (Integrations, Connected Sources, Add Data Source, field Authorization), or with POST /api/v1/oauth_providers (webhook_secret, webhook_enabled).
  • The stored value must be the entire header value, scheme included (for example Basic <base64> or Bearer <token>). A bare token is rejected at save time because it would silently fail every delivery.
  • The receiver compares in constant time after digesting both sides to a fixed length, so the comparison leaks neither the content nor the length of the secret.
  • A project with no configured secret, or with webhook delivery disabled, accepts nothing.

Payload

One JSON object per POST, at most 64 KB. Only the fields below are read; extra fields are ignored.

FieldMeaning
metricThe measurement kind. Matched case-insensitively: blood_pressure (also bp), pulse (heart_rate, hr), glucose (blood_glucose), weight (body_weight), spO2 (oxygen_saturation), temperature (body_temperature). Unknown metrics are acknowledged and skipped.
patient_idYour external identifier for the patient. It must equal the patient's identifier; a patient that does not exist yet is created in the project.
value_1, value_2Numeric strings. For blood_pressure, value_1 is systolic and value_2 is diastolic.
timestampDevice measurement time in UTC, RFC 3339. Becomes the observation's effective_datetime and the deduplication key, so a redelivered reading is idempotent. Fallbacks: an offset-less YYYY-MM-DD HH:MM:SS (read as UTC), epoch seconds, then created.
device_name, hwi_device_id, hardware_uuid, sensor_codeDevice identity; recorded as the observation's device_reference and in metadata.
timezone_offsetInformational; not applied, because timestamp is already UTC.

What is written

metricBiosignalCodeUnit written
pulse1, heart rateLOINC 8867-4beats/minute
blood_pressure3, blood pressure panelcomponents systolic and diastolic; FHIR derives the 85354-9 panelmmHg
temperature4, body temperatureLOINC 8310-5Celsius, converted from the supplier's Fahrenheit
spO25, oxygen saturationLOINC 59408-5percent
glucose14, fingerstick glucoseLOINC 41653-7mg/dL
weight24, body weightLOINC 29463-7kg, converted from the supplier's pounds

Perfusion index and irregular-heartbeat flags are not mapped today. The raw payload is preserved verbatim under a provider-named key in observation.metadata, so a converted value can always be traced to what arrived. source_id names the cellular source; method_code is left empty because the device is the method.

After the write, the reading is linked to the patient's active episodes whose program declares the signal in required_signals, and every active observation_create rule scoped to those episodes is evaluated. That is the whole response loop; see the quickstart for it end to end.

Response semantics

The supplier's hub waits a short time and does not retry, so the receiver acknowledges aggressively:

StatusWhen
200Accepted, including an unmapped metric or a data-level problem, which is logged rather than refused
400The body is not valid JSON for the payload above
401The Authorization header is missing or does not match
404Unknown project key, no cellular source configured for the project, or webhook delivery disabled

A 200 does not mean a rule fired. Check GET /api/v1/policies/{policy_id}/executions or the episode's attention.

Image ingest

POST /ingest/image (Write scope; the token must carry an environment) stores a photograph or screenshot against a patient for later classification.

{
  "xuser_id": "<patient uuid>",
  "device_id": "phone-01",
  "ts_ms": 1789740300000,
  "image_data": [255, 216, 255, 224, ...],
  "mime_type": "image/jpeg"
}
  • image_data is the raw bytes as a JSON array; mime_type must be one of image/jpeg, image/png, image/heic, image/heif; ts_ms is the capture time in milliseconds and must be positive.
  • The patient must be active, in your organization and in the token's environment; anything else is 403. An empty image, an unsupported type or a non-positive timestamp is 400.
  • Bytes are hashed; an identical image already stored for the organization returns the existing record instead of a duplicate.
  • There is no dedicated size limit on this route today (only the service-wide request cap applies); keep uploads to a few megabytes. The iOS SDK's uploadImage compresses to JPEG for you; see the BioSDK image guide.

Read back with GET /api/v1/images (filters xuser_id, session_id, date_range, limit, offset), GET /api/v1/images/{image_id}, and GET /api/v1/images/{image_id}/file for the bytes. Each record carries captured_at, image_url, thumbnail_url, a classification (result, confidence, model_version) once an image model from the Model Registry has run, and change_detected. POST /api/v1/images/reprocess (Write scope) re-queues unclassified images. A program maps classification labels to attention points with attention_config.classification_attention_map.

Waveform captures

POST /ingest/captures (Write scope, at most 8 MB) lands a client-recorded telemetry strip - today an ECG from a phone or watch - as one observation and one signal stream:

FieldMeaning
sourceapple-healthkit or oauth-fitbit
external_refThe provider's stable per-capture key; a repeat is deduplicated (deduped: true)
signal_typeecg (the only type supported today)
samples_mv, sample_rate_hz, lead_countThe waveform in millivolts
started_at, ended_atCapture window
result_classification, average_heart_rateThe device's own reading, if any
raw_payloadRequired; the audit record for the capture
device_referenceOptional device identity

The response carries observation_id, signal_stream_id and deduped.

Provenance fields

Every observation, whichever path wrote it, carries the fields a reviewer needs to know when it was measured, when it arrived, where it came from and how:

FieldMeaning
effective_datetimeWhen the measurement was taken, as the device or provider reports it. The clinical time.
created_atWhen AnyBio wrote the row. The receipt time.
measured_at, received_atSet by wearable syncs that carry both; the pair behind latency_class.
latency_classrealtime (received within 60 seconds of measurement), near_realtime (within an hour), delayed (within a day), historical (older).
trigger_eligibleWhether the reading may fire rules. realtime and near_realtime are eligible; delayed and historical are not. Paths that do not classify (the cellular webhook, the observations API) leave the class unset and the reading eligible.
source_id / source_slugWhere the data came from: the cellular source, a wearable, the iOS or web SDK, manual.
method_codeHow it was derived, when the source alone does not say; synthetic marks a fabricated reading.
device_referenceThe device that measured it.
metadataThe raw payload or provider annotations.

Rules can filter on these: trigger_config.latency_classes and trigger_config.max_data_age_minutes on an observation_create rule. Adherence and billing ledgers count device time (effective_datetime) and report the server's last_data_sync_at separately, so a late-arriving reading is credited to the day it was taken, not the day it arrived.

Not covered here

  • POST /ingest/raw-packet and the ingest session routes under /api/v1/ingest/sessions are the SDK's streaming path; see Stream on iOS.
  • POST /api/v1/observations and /batch write observations directly (Write scope) and link only to the episode_ids you pass; they do not evaluate rules. Use them for imports and journals, not to simulate a device.
  • Wearable webhooks (per-project routes under /webhooks/) are signature-verified pull triggers for specific providers; their data arrives through the sync path with latency_class set.

On this page