SlimSlim/Trends

Get trends graphs

  • Sandbox: available
  • Staging: available
  • Production: unknown
GET
/v1/slim/trends/graphs

Returns a resident's heart rate and respiratory rate over a date range, grouped by graph type and device, with threshold and baseline values and summary statistics. Use it to draw the trend charts. For live readings from the last day, use GET /v1/slim/trends/realtime.

Auth: bearer token. The caller must be a practitioner, an admin or a System Owner, and needs Organization:read, Device:read, Patient:read and DocumentReference:read. Scope: the caller's project (from the token). Observations are read with your token, so Medplum's access policy limits what you see.

Behaviour

  • graphType is a comma-separated list of heartRate and respiratoryRate. Repeated entries are ignored. startDate must not be after endDate.
  • Days are cut at the project's time zone (default Europe/Berlin). The chart for a day starts at 09:00 local time, so a night stays in one day.
  • A range of two or more days is an over-day chart: one point per day, at 09:00 local time, starting the day after startDate. Each point is the day's mean from the pre-computed statistics, with batch readings filling nights that have none.
  • A range of one day or less is an in-day chart: 30-second points across the 24 hours that start at 09:00 local time on startDate. endDate only decides which kind of chart you get. In-day points come from batch readings only.
  • Before a night's recording is picked, both charts drop the readings and sleep statistics that end outside the resident's use periods of their device, as GET /v1/slim/sleep/statistics does. So a previous resident's night that ended before the device was handed over, but reached Ovok after it and was stored under this resident, is not charted. A resident with no device use history keeps all of them.
  • Once a resident has any use period, this also drops the readings of a device with no period of theirs, and those from before a device's earliest period. A device assigned before use periods were recorded gets its first one on its next edit in Ovok, starting at the Device's last write before that edit, so its readings from before then are not charted. Each device whose readings in a request all drop is counted in the sleep.device_use_device_dropped metric and logged at most once a minute.
  • The use periods are read on every uncached call. When Medplum answers that read with an error, the route answers 502 rather than chart a night it cannot check; when the request itself fails, the route fails with Medplum's status, or 500.
  • When a night (the calendar day its recordings end on, in the project's time zone) has several sleep recordings left, both charts show only the readings of the one whose sleep statistics were written last (latest meta.lastUpdated): its heart and respiratory rate statistics, matched by the recording's exact period, and its batch readings, matched by their start (below). That is the rule GET /v1/slim/sleep/statistics uses. A night with no sleep statistics goes by its heart and respiratory rate statistics' write times. A night with one recording left, a night with batch readings only, and a night none of whose readings belongs to the chosen recording are charted with all the readings left. Readings with no end time cannot be put on a night, so neither rule applies to them: they are always charted.
  • Sleep statistics are read over the chart's range, which ends at 09:00 local time on its last day, so a recording that starts later that day is not weighed.
  • A batch block is matched by its start, because Sleepiz ends one up to about ten minutes before its recording: it belongs to the recording that starts at the same instant and ends at or after it, at most 15 minutes later, the earliest such end when two recordings share the start. A batch block that starts at any other time belongs to no recording, so on a night with several recordings it is dropped: a chosen recording whose only heart rate reading is a batch block that starts after the recording shows no heart rate for that night, while its respiratory rate still shows.
  • Every bucket is returned. A bucket without a reading has data: null. pointTimestamp and pointTimestampRange show which readings filled it.
  • maxThreshold, minThreshold, maxBaseLine and minBaseLine are the values that applied at that time. They are left out when none apply, and when the threshold lookup fails the chart still returns without them.
  • When a heart or breathing rate limit changed inside the range (a nurse's edit or a home-wide save), each point carries the limit in force at its own time, as Signals recorded it, so the lines step where the limit changed. Points after the last change carry the limits the limits screen shows. If Signals' change history cannot be read, every point carries the current limits.
  • Devices are keyed <manufacturer>-<device id>. A resident who changed device has one entry per device.
  • A graph type with no data in the range comes back as {}. When nothing at all can be charted, both heartRate and respiratoryRate are {}.
  • Results are cached for up to 60 seconds. A write to the resident's data invalidates the cache sooner.

Example

curl -X GET 'https://api.sandbox.ovok.com/v1/slim/trends/graphs?patientId=c3499c27-2973-0a7f-807e-fb8676a92dcb&startDate=2026-09-01T00:00:00Z&endDate=2026-09-30T00:00:00Z&graphType=heartRate,respiratoryRate' \  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'
{  "heartRate": {    "Sleepiz-5f0c...": {      "dataPoints": [        {          "timestamp": "2026-09-02T07:00:00.000Z",          "timestampMs": 1788332400000,          "data": 72,          "maxThreshold": 100,          "minThreshold": 60,          "pointTimestamp": "2026-09-02T05:41:00.000Z"        }      ],      "stats": { "average": 72, "max": 72, "min": 72, "total": 1 }    }  },  "respiratoryRate": {}}

Authorization

bearer
headerAuthorizationBearer <JWT>

Query Parameters

patientId*string
Formatuuid
startDate*string
Formatdate-time
endDate*string
Formatdate-time
graphType*string

Response Body

Successfully retrieved trends data grouped by device and graph type

application/json
  1. response

Trends data grouped by graph type and device ID

[key: string]?
Example Request
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/trends/graphs?patientId=497f6eca-6276-4993-bfeb-53cbbbba6f08&startDate=2019-08-24T14%3A15%3A22Z&endDate=2019-08-24T14%3A15%3A22Z&graphType=string'
Example Response
{  "property1": {    "property1": {      "dataPoints": [        {          "timestamp": "string",          "timestampMs": 0,          "data": 0,          "maxThreshold": 0,          "minThreshold": 0,          "maxBaseLine": 0,          "minBaseLine": 0,          "pointTimestamp": "string",          "pointTimestampRange": {            "start": "string",            "end": "string"          }        }      ],      "stats": {        "average": 0,        "max": 0,        "min": 0,        "total": 0      }    },    "property2": {      "dataPoints": [        {          "timestamp": "string",          "timestampMs": 0,          "data": 0,          "maxThreshold": 0,          "minThreshold": 0,          "maxBaseLine": 0,          "minBaseLine": 0,          "pointTimestamp": "string",          "pointTimestampRange": {            "start": "string",            "end": "string"          }        }      ],      "stats": {        "average": 0,        "max": 0,        "min": 0,        "total": 0      }    }  },  "property2": {    "property1": {      "dataPoints": [        {          "timestamp": "string",          "timestampMs": 0,          "data": 0,          "maxThreshold": 0,          "minThreshold": 0,          "maxBaseLine": 0,          "minBaseLine": 0,          "pointTimestamp": "string",          "pointTimestampRange": {            "start": "string",            "end": "string"          }        }      ],      "stats": {        "average": 0,        "max": 0,        "min": 0,        "total": 0      }    },    "property2": {      "dataPoints": [        {          "timestamp": "string",          "timestampMs": 0,          "data": 0,          "maxThreshold": 0,          "minThreshold": 0,          "maxBaseLine": 0,          "minBaseLine": 0,          "pointTimestamp": "string",          "pointTimestampRange": {            "start": "string",            "end": "string"          }        }      ],      "stats": {        "average": 0,        "max": 0,        "min": 0,        "total": 0      }    }  }}

Update threshold PUT

Replaces the home's threshold configuration: the alert limits every resident follows unless they have limits of their own. > **Deprecated.** Use `PUT /v2/slim/threshold`. This v1 route stays available until every client has moved. **Auth:** Bearer token. The caller must be a practitioner, an admin, a super admin, or hold the "System Owner" access policy. The access policy must grant `PlanDefinition:update` (an admin or super admin who holds no access policy skips this check). **Scope:** The caller's project (from the token). ## Behaviour - The body replaces the stored settings; fields you omit take their schema defaults, except the out-of-bed window: a body without `durationOfOutOfBed.period`, or without `durationOfOutOfBed`, keeps the home's stored window (19:00:00 to 09:00:00 when it has none). The home's limit ranges and optional vitals, which v1 cannot send, are kept as stored. - Units: `durationOfOutOfBed` is in seconds, `sleepDuration` in hours. - `enabled: false` switches a metric fully off: its `absolute` and `relative` bands are stored off too, whatever their own `enabled` says, and the residents following the home hold no Signals limit for it (a resident with limits of their own keeps them). Out of bed keeps its `relative` band, which only tightens its limit while it is on. - To switch a metric back on, send its band's `enabled: true` as well. A switch-off stores the band's flag `false`, so a body that sets only the metric's `enabled: true` reads back `enabled: true` but arms nothing: the residents following the home get no limit for it. - `meanHeartRate` and `meanRespiratoryRate` relative bands cannot be enabled for the home: a resident without a baseline of their own would get no alarm from them. Set baseline limits per resident. One sent under `enabled: false` is stored off. - Every limit is evaluated by Signals, so every save is all or nothing, as `PUT /v2/slim/threshold` describes: it answers once Signals holds the home's limits, which every resident following the home follows at once, or puts everything back and answers an error with `saved: false` (409 another save or a tenant move got in the way; 502 out of time, or Signals did not take them; 500 the undo failed too). Nothing is stored then; send the save again. A 504 from the gateway can come while a failed save is still being put back: read the limits before sending it again. - A change-history entry is written for a save that stands. - The response is the saved configuration. ## Example ```bash curl -X PUT 'https://api.sandbox.ovok.com/v1/slim/threshold' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "status": "active", "settings": { "heartRate": { "mode": "organization", "enabled": true, "absolute": { "enabled": true, "min": 40, "max": 120 } } } }' ```

Get realtime trends graphs GET

Returns the resident's readings from Signals, one data point per reading, in the same shape as `GET /v1/slim/trends/graphs`. Use it for the live part of a chart, or to look at any day of the last 14. **Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Organization:read`, `Device:read`, `Patient:read` and `DocumentReference:read`. **Scope:** the resident must be in the caller's project or one of its sub-projects (not its parent). A resident outside that scope answers `404`, the same as one that does not exist. ## Behaviour - The chart covers exactly `[startTime, endTime]`. Unlike `/graphs`, it does not snap to monitoring days. - Every reading in the window comes back as Signals recorded it, oldest first: one data point per reading, at the reading's own time, with its own value. Nothing is bucketed or averaged, and no empty points are added between readings, so a silent device shows as a stretch with no points. A reading with no numeric value comes back with `data: null`. - The response grows with the device's cadence: a day of readings every 30 seconds is about 2,900 points per graph type. - The readings are the resident's, whatever device they wear now: a resident whose device was unassigned or moved to someone else, or who was discharged, still gets every reading Signals holds for them in the window. A resident who takes over a device gets only the readings sent after they took it. - Readings are charted under one key: the resident's most recently assigned device, keyed as on `/graphs`. A resident with no device now is charted under the device they wore last, so the key does not change when the device is taken away, and a resident who never wore one under `Patient/<id>`. Signals readings carry no device, so a resident whose device changed inside the window shows one key here where `/graphs` shows one per device. - Threshold and baseline values are the same as `/graphs`. `stats` are computed over every reading in the window. - Every graph type in the table below can be charted. Only `heartRate` and `respiratoryRate` carry threshold and baseline values. Each point carries the limit in force at its own time, as on `/graphs`, so the lines step where a limit changed inside the window. - `startTime` must not be after `endTime`, the window may span at most 24 hours, and `startTime` must not be more than 14 days ago. A request that breaks one of these fails validation. - Readings are charted by when they were observed, or by when Signals stored them when no observed time exists. Late-arriving readings are included. - A graph type with no readings in the window is `{}`. - If Signals cannot be reached, does not answer within 12 seconds, or holds more readings than one request can page through, the route returns `503` with `error: "signals_unavailable"` and a `retryAfterSec` value, rather than a partial or empty chart. - Results are cached for up to 15 seconds. Both request times are rounded down to that interval, and the readings returned are those of the rounded window, so a repeated poll can show a window up to 15 seconds old. A result over 128 KB (most full days) is not cached. - A chart reads this once per page load. Each caller may make 20 requests a minute; past that the route returns `429` with `Retry-After`. When too many realtime reads are already running on the server, it returns `503` with `error: "signals_unavailable"` and `retryAfterSec` at once, before reading anything. ## Graph types Each type charts every Signals reading recorded under one of its codes. The names are the dashboard telemetry's, so pulse rate by pulse oximetry is `pulseRate` and never part of `heartRate`. | `graphType` | Codes | | ------------------------ | ---------------------------------------------------------------- | | `heartRate` | `8867-4`, `43149-4` | | `respiratoryRate` | `9279-1` | | `oxygenSaturation` | `59408-5`, `2708-6` | | `pulseRate` | `8889-8` (heart rate by pulse oximetry) | | `perfusionIndex` | `61006-3`, `PI` (Kinderspitex's own vital code, not LOINC) | | `bodyTemperature` | `8310-5` | | `systolicBloodPressure` | `8480-6` | | `diastolicBloodPressure` | `8462-4` | | `heartRateVariability` | `80404-7` | ## Example cURL request ```bash curl -X GET \ 'https://api.sandbox.ovok.com/v1/slim/trends/realtime?patientId=c3499c27-2973-0a7f-807e-fb8676a92dcb&startTime=2026-01-01T12:00:00Z&endTime=2026-01-02T12:00:00Z&graphType=heartRate,respiratoryRate' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```