Submit a reading by patient refId
Submit a reading by patient refId, through Ovok's Signals gateway.
Submit a reading by patient refId
| Method | Path |
|---|---|
POST | /v1/signals/api/observation |
- Sandbox: available
- Staging: available
- Production: unknown
The patient is patientRefId in the body, else subject.reference (Patient/<refId>).
The body is a FHIR R4 Observation: code, value[x] (valueQuantity, valueInteger, valueString, valueBoolean, valueDateTime, valueCodeableConcept) or dataAbsentReason, and a combined device sample's vitals as component[] (each code, value[x], optional dataAbsentReason). The clinical time is effectiveDateTime, effectiveInstant or effectivePeriod.start, else issued, else the server's receipt; device is {reference, display}; the first identifier's value is stored as v1's refId. Other FHIR elements (meta, extension, performer, interpretation, referenceRange, …) are accepted and ignored. v1's observedAt (it wins over effective[x]), refId, string dataAbsentReason and device.id still work. A node with dataAbsentReason and no value is judged as carrying no value. valueSampledData is judged by its latest sample and stored whole. A valueDateTime, on the reading or a component, is judged and aggregated as epoch ms (valueNumber) and listed as sent.
History is never judged. A reading whose category has a coding the tenant names as history (its historyCategories; the default pack's: batch-data, statistics, presence-batch, derived) is stored and deduplicated like any other, answered with no alerts, and never judged nor read by the rules.
The caller must be one of the patient's submitters, admins included: a known patient it is not attached to is a 403, an unknown one a 404 with type PATIENT_NOT_FOUND.
Idempotent. A reading is named by its first identifier (system|value), else by its clinical time, code, device and refId. A resend of one stored within the last hour is a 409 DUPLICATE_READING whose data.observationId is the stored reading, and is neither stored nor evaluated again. A reading with neither an identifier nor a clinical time is not deduplicated.
Concurrent requests are batched server-side. With Prefer: respond-sync evaluation finishes first and the reply adds alertIds, alerts ([{id, code}]), resolvedAlertIds and ruleCoverage; without it mode is async and alerts are delivered by webhook.
Through ovok
- Who can call it: Submitter apps, and users whose access policy grants the capabilities.
- Capabilities, all required:
signals_observations:create. Project admins have every capability; client applications get no admin bypass. - If your access covers only some patients (anyone but project admins, manager or submitter apps, and anyone whose Patient access has no criteria), every patient the body names must be one you can update. A patient is named by its Signals id, or by its Medplum Patient id where Signals takes a
refId; ovok asks Signals which patient a Signals id is. - Each reading counts its patient as monitored for billing.
Request
Body
Required. Schema V1ObservationByRefDto.
| Field | Type | Required | Description |
|---|---|---|---|
patientRefId | string | No | — |
resourceType | string | No | — |
identifier | array of object | No | — |
identifier[].system | string | No | — |
identifier[].value | string | No | — |
refId | string | No | — |
status | string | No | — |
category | array of object | No | Its codings' codes are stored; a code the tenant names in historyCategories makes the reading history: stored, never judged. |
note | object | No | Accepted as a plain string or as FHIR's [{text, time}] Annotations, which the mapper flattens to newline-joined text. Deliberately untyped: typing it as a string would reject real readings ovok sends today. |
code | object | Yes | — |
code.coding | array of object | No | — |
code.text | string | No | — |
subject | object | No | — |
subject.reference | string | No | — |
subject.display | string | No | — |
component | array of object | No | — |
component[].code | object | Yes | — |
component[].dataAbsentReason | object | No | — |
component[].valueQuantity | object | No | — |
component[].valueSampledData | object | No | — |
component[].valueString | string | No | — |
component[].valueInteger | number | No | — |
component[].valueBoolean | boolean | No | — |
component[].valueDateTime | string | No | — |
component[].valueCodeableConcept | object | No | — |
device | object | No | id (v1) or reference (FHIR) names the device. |
dataAbsentReason | object | No | v1's string or FHIR's CodeableConcept; untyped so that neither is refused. |
observedAt | string | No | — |
effectiveDateTime | string | No | — |
effectiveInstant | string | No | — |
effectivePeriod | object | No | Only .start and .end are read, as in v1. |
issued | string | No | — |
valueQuantity | object | No | — |
valueQuantity.value | number | No | — |
valueQuantity.unit | string | No | — |
valueQuantity.system | string | No | — |
valueQuantity.code | string | No | — |
valueSampledData | object | No | — |
valueSampledData.origin | object | No | — |
valueSampledData.period | number | No | — |
valueSampledData.factor | number | No | — |
valueSampledData.lowerLimit | number | No | — |
valueSampledData.upperLimit | number | No | — |
valueSampledData.dimensions | number | No | — |
valueSampledData.data | string | No | Space-separated samples, oldest first; E, L and U are no sample. |
valueString | string | No | — |
valueInteger | number | No | — |
valueBoolean | boolean | No | — |
valueDateTime | string | No | — |
valueCodeableConcept | object | No | — |
valueCodeableConcept.coding | array of object | No | — |
valueCodeableConcept.text | string | No | — |
Responses
| Status | Meaning |
|---|---|
200 | The reading as stored, plus the ingest outcome. |
400 | The query carries a raw #, or more than 200 parameters. |
401 | Your ovok access token, or your client id and secret, are missing or invalid. |
403 | ovok refused the call: you lack a capability this route needs, or your session has no project, or your access covers only some patients and the call names none, or you can read a patient the call names but not update it. From Signals: Authenticated, but the client's role does not permit this, or it is not attached to the patient. |
404 | A patient the call names is not one you can read. From Signals: No patient with that refId in this tenant. |
409 | Your project has no Signals tenant of its own (SIGNALS_TENANT_NOT_OWN), or it is moving to another one. From Signals: A resend of a reading already stored (DUPLICATE_READING, data.observationId); nothing was stored or evaluated. |
415 | The body is not JSON. |
422 | A patient id is not 1 to 64 letters, digits, ., _, ~ or -. More than 100 patients are named. A patient is named by something other than a string. From Signals: The payload failed validation, or named no patient. |
429 | Too many requests. Retry after the seconds in Retry-After. |
500 | ovok hit an unexpected fault, such as your project having no Signals submitter. |
502 | Signals answered with something ovok couldn't use, or refused ovok's own credential. |
503 | Signals is unavailable. Retry after the seconds in Retry-After. From Signals: The evaluation backlog is full, or the readings could not be stored and judged (no database connection free in time, a lock timeout, a stalled database). Nothing was stored; resend after Retry-After (2 s). |
504 | Signals did not answer in time. |
Example
curl --request POST 'https://api.sandbox.ovok.com/v1/signals/api/observation' \
--user "${OVOK_CLIENT_ID}:${OVOK_CLIENT_SECRET}" \
--header 'Content-Type: application/json' \
--data @body.jsonLast updated on