SlimSlim/Change History

List change history

  • Sandbox: available
  • Staging: available
  • Production: unknown
GET
/v1/slim/change-history

Lists the recorded changes to thresholds, locations, zones, devices, residents, and users and their roles in your project, newest first. Use it for the resident and administration change-history screens.

Auth: bearer token. The caller must be a practitioner, an admin or a System Owner, and needs Provenance:read. Only the Admin and System Owner access policies carry it, so other users get no change history. Scope: the caller's project (from the token). The rows are read with your token, so you only see changes recorded in your project.

Behaviour

  • Each row is one recorded change. details.key names the change and details.values holds the values to put into its message.
  • Rows are sorted by recording time, newest first. sort=asc returns the same rows oldest first. Rows recorded in the same millisecond keep one fixed order, so paging never skips or repeats a row.
  • Without startDate, the list starts 30 days before now. endDate is optional. Both bounds are inclusive.
  • types limits the rows to the given change types. Omit it to get every type. Repeat the parameter to pass several types.
  • scope=global keeps only project-wide changes: organization threshold edits, device, location and zone lifecycle changes, and user and role changes. scope=all (the default) returns every row.
  • types=Access lists the user and role changes: a user invited (userInvited), renamed (userRenamed), moved to another role (userRoleChanged) or removed (userRemoved), and a role created (roleCreated), given other permissions (rolePermissionsChanged) or deleted (roleDeleted). They name users and roles by name and concern no resident.
  • userId, residentId, deviceId, locationId and zoneId are ids, not references. Each takes several ids, repeated or comma-separated, and matches a row concerning any of them. Different filters combine with AND. userId matches the practitioner who made the change. A value that is not an id answers 422.
  • relatedTo takes typed references (Patient/<id>, Device/<id>, Location/<id>, Organization/<id>), repeated or comma-separated, and returns every row concerning any of them, once each.
  • actions keeps the kinds of change you name: created, deleted, renamed, moved, assigned, unassigned, discharged, reactivated, thresholdChanged. Rows recorded before this filter shipped carry no kind, so they never match it.
  • resident on a row is the resident it concerns, {patientId, residentId}, or null when it concerns none. residentId inside it is their Resident ID, or null when they have none: show a placeholder, never a name. The flat residentId carries the same Resident ID, or is empty. Neither is ever a name or an internal id.
  • Threshold and Signals settings changes made through the Signals API are listed as Threshold changes, with the reason given.
  • Changes a device makes itself, such as a baseline re-push after a new 7-day mean, are never listed, and never counted in total.
  • Unknown query parameters are ignored.
  • page is zero-based and count defaults to 50, maximum 100. total counts only rows the list can return, and never exceeds 10,000. Pages from offset 10,000 on come back empty, so narrow the filters or the date range to reach older rows.

Example

curl -X GET 'https://api.sandbox.ovok.com/v1/slim/change-history?types=Threshold&types=Resident&page=0&count=50' \  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk'

Authorization

bearer
headerAuthorizationBearer <JWT>

Query Parameters

page?string
Default"0"
count?string
Default"50"
types?|array<>

Filter by one or more change types. Omit to include every type — the dashboard shows everything by default.

scope?string

Resident-level vs administration-level filter. all returns every row the actor is allowed to see (default — used by the resident change-history page). global narrows to project-wide events such as org-threshold edits — used by the administration change-history page.

Default"all"
Value in"all""global"
userId?|array<string>

Filter by the practitioners who made the change: any of them.

residentId?|array<string>

Filter by the residents (patients) the change concerns: any of them.

deviceId?|array<string>

Filter by the devices the change concerns: any of them.

locationId?|array<string>

Filter by the locations the change concerns: any of them.

zoneId?|array<string>

Filter by the zones (organizations) the change concerns: any of them.

relatedTo?|array<string>

Rows concerning any of these references (Patient/<id>, Device/<id>, Location/<id>, Organization/<id>).

actions?|array<string>

Filter by the kind of change: any of these. Only rows recorded since it exists carry one.

sort?string

Recording time order: desc (default) newest first, asc oldest first.

Default"desc"
Value in"asc""desc"
startDate?string

Only include changes recorded at or after this ISO timestamp. Defaults to 30 days ago when omitted.

Formatdate-time
endDate?string

Only include changes recorded at or before this ISO timestamp.

Formatdate-time

Response Body

Change history rows retrieved successfully.

application/json
  1. response
total*number

Total number of matching rows across all pages.

page*number

Zero-based page index returned.

count*number

Number of rows returned in this page.

resources*array<>
Example Request
curl -X GET 'https://api.sandbox.ovok.com/v1/slim/change-history'
Example Response
{  "total": 0,  "page": 0,  "count": 0,  "resources": [    {      "id": "string",      "type": "Threshold",      "user": "string",      "residentId": "string",      "resident": {        "patientId": "string",        "residentId": "string"      },      "deviceId": "string",      "locationId": "string",      "zoneId": "string",      "time": "2019-08-24T14:15:22Z",      "details": {        "key": "string",        "values": {          "property1": "string",          "property2": "string"        }      }    }  ]}

Backfill missed Sleepiz nights POST

Refills Sleepiz nights that the live push never delivered. Ovok pulls each night from the Sleepiz public API for one device and sends it through the same ingest path as live data. **Auth:** Bearer token. The caller must be a project admin. **Scope:** The caller's project (from the token) and its sub-projects. The device must belong to one of them. The route takes no project parameter. ## Behaviour - The body is strict: `serialNumber` (6 to 10 digits), `from` and `to` (`YYYY-MM-DD`). Unknown fields are rejected. - `from` to `to` is inclusive, `from` must not be after `to`, `to` must be yesterday (UTC) or earlier, and the range is at most 7 nights. Longer gaps need several calls. - The device is looked up by its serial. It must be a Sleepiz serial, must belong to your project or one of its sub-projects (403 otherwise), and must have an assigned resident (409 otherwise). Assign the resident and call again. A serial held by devices in more than one project is refused with 409 too: ingest routes its readings to none of them. - Nights are matched to the device's patient deployments in Sleepiz. Each night in the response has an `outcome`: `posted`, `no_recording`, `no_deployment` or `error`. One night failing does not stop the others. - **`posted` means queued, not stored.** Ovok queues the night's Observations through the live ingest router and nothing is written to Medplum during the request. `jobIds` lists the queued jobs and `missingBlocks` names data Sleepiz had no samples for. - Re-running the same device and range is safe. The queue job ids are deterministic and the Observation write is conditional on its event id, so no duplicates appear. - The route answers 503 when the Sleepiz credentials are not configured on the server, and 502 when the Sleepiz API cannot be reached. - **Last night needs no call.** Every day at 11:00 UTC ovok runs the same refill for yesterday's night on every device that has a resident and no recording of that night yet, except the stakeholder beds. Use this route for older nights. ## Example ```bash curl -X POST 'https://api.sandbox.ovok.com/sleepiz/backfill' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' \ -H 'Content-Type: application/json' \ -d '{ "serialNumber": "<serial>", "from": "2026-01-01", "to": "2026-01-03" }' ```

List projects for device assignment GET

Lists the projects you can move a device into: your own project, its parent project and its sub-projects. **Auth:** bearer token. The caller must be a practitioner, an admin or a System Owner, and needs `Device:update`. **Scope:** the caller's project (from the token) and its parent and sub-projects. ## Behaviour - Each entry is `{ project: { id, name } }`. `name` is an empty string when the project has none. - `total` is the number of projects returned. - Read-only. ## Example ```bash curl -X GET 'https://api.sandbox.ovok.com/v1/slim/device/assign/project' \ -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NjcyNjg4MDAsImV4cCI6MTc2NzI3MjQwMH0.Ql3B_ztDvctlyZ2z0vp4sl3ExXRQGkzmxdmTAqxNzZk' ```