Signals API - Observations

List a patient's readings

  • Sandbox: available
  • Staging: available
  • Production: unknown
GET
/v1/signals/api/observation/{patientId}

Newest first by clinical time, in v1's envelope {items, total, page, pageSize, totalPages} with nest's nextCursor. from, to and code (comma-separated) filter as in v1; category (comma-separated) keeps the readings carrying any of those FHIR category codes. With from, a reading whose clinical time is more than an hour ahead of when it was stored is not found: the read skips the storage older than that, so it never waits on it. pageSize defaults to 10; above 200 it needs both from and to, and then reaches 10000. A page may start at most 10000 rows in; walk further with cursor.

Each item is a flat timeline entry (code, valueNumber, observedAt, category, valueSampledData, components, effectiveStart, effectiveEnd, deviceId, …) with v1's _id, id, patient, valueQuantity and createdAt beside it. fields=code,valueNumber returns exactly those keys instead.

Through ovok

  • Who can call it: Manager apps, and users whose access policy grants the capabilities.
  • Capabilities, all required: signals_observations:read. 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), the patient must be one you can read. 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.

Authorization

headerAuthorizationBearer <JWT>

Path Parameters

patientId*string

The Signals patient id.

Query Parameters

page?number

1-based, as v1 paged. A page may start at most 10000 rows in; walk further with cursor.

Range1 <= value
pageSize?number

v1's page size; defaults to 10. Wins over limit when both are sent.

Range1 <= value
from?string

Inclusive lower bound on clinical time; a reading timed over an hour after it was stored is missed.

to?string

Inclusive upper bound on the reading's clinical time.

code?string

One LOINC code, or several separated by commas.

Lengthlength <= 1000
category?string

One FHIR category code, or several separated by commas; a reading carrying any of them.

Lengthlength <= 1000
limit?number
Range1 <= value
cursor?string
Lengthlength <= 512
fields?string

Only these keys of each item, separated by commas (code,valueNumber,observedAt). Every key when absent. A chart reading a day of readings needs a few of them.

Lengthlength <= 200

Response Body

One page of readings.

application/json
  1. response
items*array<>

Exactly the fields keys instead, when fields is sent.

total*number
page*number

1-based.

pageSize*number
totalPages*number
nextCursor*string

Opaque keyset cursor.

Example Request
curl -X GET 'https://api.sandbox.ovok.com/v1/signals/api/observation/string'
Example Response
{  "items": [    {      "observationId": "string",      "valueNumber": 0,      "valueString": "string",      "valueUnit": "string",      "note": "string",      "observedAt": "2019-08-24T14:15:22Z",      "recordedAt": "2019-08-24T14:15:22Z",      "code": "string",      "system": "string",      "display": "string",      "status": "string",      "components": [        {          "code": "string",          "system": "string",          "display": "string",          "valueNumber": 0,          "valueUnit": "string",          "valueString": "string",          "valueDateTime": "2019-08-24T14:15:22Z",          "valueSampledData": {},          "dataAbsentReason": "string",          "sequence": 0        }      ],      "category": [        "string"      ],      "valueSampledData": {},      "valueDateTime": "2019-08-24T14:15:22Z",      "effectiveStart": "2019-08-24T14:15:22Z",      "effectiveEnd": "2019-08-24T14:15:22Z",      "deviceId": "string",      "_id": "string",      "id": "string",      "patient": "string",      "valueQuantity": {},      "createdAt": "2019-08-24T14:15:22Z"    }  ],  "total": 0,  "page": 0,  "pageSize": 0,  "totalPages": 0,  "nextCursor": "string"}

Submit a reading by patient refId POST

The patient is `patientRefId` in the body, else `subject.reference` (`Patient/<refId>`). The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) 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, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`. **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.

Submit a reading for a patient id POST

`subject` is ignored: the path names the patient. The body is a FHIR R4 Observation: `code`, `value[x]` (any of FHIR R4's eleven) 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, and the rules read the reason as `absent`. `valueSampledData` is judged by its latest sample and stored whole, and the rules read every sample as `samples`. A `valueDateTime`, on the reading or a component, is judged and aggregated as epoch ms (`valueNumber`) and listed as sent. A `valueBoolean` is judged as 1 or 0. A `valueTime` is judged as ms since midnight, a `valuePeriod` as its length in ms (none while open or without an offset), a `valueRatio` as its quotient, and each is also kept as text (`valueString`); a `valueRange` is judged as the text `low..high`. **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), the patient 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.