Update tenant settings
- Sandbox: available
- Staging: available
- Production: unknown
settings is merged, so a switch left out is left alone. Takes effect on the next request — except throttleLimit, which each replica caches for up to a minute.
episodicAlerts gates episode recording and the notification feed. synchronousMode makes ingest return before evaluating. throttleLimit overrides THROTTLE_LIMIT for this tenant; 0 exempts it.
informationalCodes, historyCategories and feedCodes (lists of strings, none by default): the codes the tenant's triggers consume and never judge (no no-verdict, and the * trigger skips them), the reading categories stored and never judged, and the codes the notification feed lists by default (none: every code). A new tenant is given the default pack's lists, and a tenant that states no historyCategories keeps the default pack's.
ruleSet is deprecated and ignored: readings are judged by the triggers in the rule settings, the tenant's (orgSettings) and each patient's own (/api/patient/{id}/settings).
orgSettings is the tenant's rule settings, which replace /api/settings/org: what the triggers judge by, the triggers themselves included. It is merged key by key: a key sent replaces the stored one whole, null removes it, and a key left out is kept. Its first write is given the default pack when it sends no triggers. Audited as UPDATE_ORG_SETTINGS. A band it changes ends the episodes judged under the old one, and a code it switches off (its band "NO_THRESHOLD", or out of bed enabled: false) has its active alerts resolved and its open episodes closed THRESHOLD_CHANGED at the save, for every patient judged by the tenant's settings (an acknowledged out-of-bed absence, whose episode the ack closed, has its still-active alert resolved); a band deleted closes nothing. A tenant whose only org settings are one project's, with no patient stamped with another project, has them made the tenant's first, so the write reaches the patients they served.
resolvedAt (ISO 8601) is when the caller resolved orgSettings: it lands only over a write resolved earlier, or stored without one, and is otherwise refused 409 STALE_SETTINGS with nothing written. With ifAbsent=true each key of orgSettings is written only where the stored settings lack it, so a default seeded at enrolment cannot overwrite a concurrent save.
Through ovok
- Who can call it: Manager apps, and users whose access policy grants the capabilities.
- Capabilities, all required:
signals_config:update,signals_silence:update. Project admins have every capability; client applications get no admin bypass. - Only
orgSettings,resolvedAtandsettings.episodicAlertsare written through ovok. TurningepisodicAlertsoff stops Signals recording episodes, which empties episode feeds. - It can reach any patient of your project without naming one, so it is for callers that reach every patient: project admins, manager or submitter apps, and anyone whose Patient access has no criteria.
AuthorizationBearer <JWT>ifAbsent?booleanWrite each key of orgSettings only where the stored settings lack it.
application/json- body
settings?orgSettings?The tenant's rule settings, merged key by key: a key sent replaces the stored one, null removes it.
resolvedAt?stringWhen the caller resolved orgSettings: it lands only over a write resolved earlier.
The tenant as it now stands.
application/json- response
id*stringname*stringsettings*Feature switches. episodicAlerts gates episode recording and the notification feed; synchronousMode returns before evaluating; informationalCodes, historyCategories and feedCodes are the codes and categories the tenant declares (PATCH /api/tenant); ruleSet, when present, is deprecated and ignored.
orgSettings*The tenant's rule settings, {} when none were written: what the triggers judge by, the triggers themselves included. Written through PATCH /api/tenant. Null for a SUBMITTER or SUBSCRIBER credential.
createdAt*stringdate-timeupdatedAt*stringdate-timecurl -X PATCH 'https://api.sandbox.ovok.com/v1/signals/api/tenant' \ -H 'Content-Type: application/json' \ -d '{}'{ "id": "string", "name": "string", "settings": {}, "orgSettings": {}, "createdAt": "2019-08-24T14:15:22Z", "updatedAt": "2019-08-24T14:15:22Z"}Get the caller's tenant GET
The tenant is resolved from the credential; there is no id to pass. `orgSettings` is its tenant-wide rule settings, `{}` when none were written. ### Through ovok - **Who can call it:** Manager apps, and users whose access policy grants the capabilities. - **Capabilities, all required:** `signals_config:read`. Project admins have every capability; client applications get no admin bypass.
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" }' ```