Update patient threshold (v2)
| Method | Path |
|---|---|
PUT | /v2/slim/threshold/patient/:patientId |
Authentication · Access policies
Saves one resident's own thresholds in the v2 shape. mode is required on every settings field and decides which other values the field takes.
This route supersedes PUT /v1/slim/threshold/patient/{patientId}. Only it can set a baseline limit for heart or respiratory rate, the relative out-of-bed band and the optional vitals.
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 Goal:update (admins and super admins skip this check).
Scope: The resident must be in the caller's project (from the token). A resident in another project (a sub-project included) or an unknown id answers 404. A resident who carries no project answers 403.
Request
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
patientId | string (uuid) | Yes | The resident's Patient id. |
Body
A field in organization mode takes no other keys: strip the resolved values a GET returns for it before sending it back. Inside each metric object, an unlisted key answers 422, except the response-only keys marked "ignored". Keys not listed below are ignored at the top level and inside settings; a ranges key is ignored, because ranges belong to the home.
| Name | Type | Required | Description |
|---|---|---|---|
settings | object | Yes | The resident's limits. |
settings.heartRate | object | Yes | Heart-rate limit. |
settings.heartRate.mode | "organization" | "patient" | "baseline" | Yes | organization follows the home: send no other key. patient is the resident's own absolute band. baseline is a band relative to the resident's own baseline. |
settings.heartRate.enabled | boolean | Yes for patient and baseline | Whether the limit alerts. |
settings.heartRate.min | number | Yes for patient | Lower bound in beats per minute, 20 to 300 and inside the home's heart-rate range (default 40 to 120). Must be below max when enabled is true. |
settings.heartRate.max | number | Yes for patient | Upper bound in beats per minute, 20 to 300 and inside the home's heart-rate range. |
settings.heartRate.deviationPercent | integer | Yes for baseline | The symmetric deviation from the resident's baseline, 5 to 60 percent. |
settings.heartRate.baselineAverage | number | null | No | baseline only. Response-only; ignored. |
settings.heartRate.effectiveBand | object | null | No | baseline only. Response-only ({ min, max }); ignored. |
settings.respiratoryRate | object | Yes | Respiratory-rate limit. Same fields as settings.heartRate. |
settings.respiratoryRate.min, settings.respiratoryRate.max | number | Yes for patient | Bounds in breaths per minute, 4 to 150 and inside the home's respiratory-rate range (default 4 to 60). |
settings.outOfBed | object | Yes | Out-of-bed limit. |
settings.outOfBed.mode | "organization" | "patient" | Yes | organization follows the home: send no other key. patient is the resident's own limit. |
settings.outOfBed.enabled | boolean | Yes for patient | Whether out-of-bed alerting is on. |
settings.outOfBed.maxDurationSeconds | integer | Yes for patient | The longest exit allowed, in seconds. 0 to 86400, a multiple of 60. Must be above 0 when enabled is true. |
settings.outOfBed.deviationPercent | integer | null | No | patient only. Alert sooner when an exit runs this many percent past the resident's usual one. 5 to 300. null switches it off. Left out, the stored value stays (the home's, when the resident had none). |
settings.outOfBed.baselineAverage | number | null | No | patient only. Response-only; ignored. |
settings.outOfBed.effectiveMaxDurationSeconds | number | null | No | patient only. Response-only; ignored. |
settings.bedTimePeriod | object | Yes | The window in which out-of-bed is checked. |
settings.bedTimePeriod.mode | "organization" | "patient" | Yes | organization follows the home's window: send no other key. patient is the resident's own window. |
settings.bedTimePeriod.start | string (HH:MM:SS) | Yes for patient | Window start, wall-clock time in the home's time zone. |
settings.bedTimePeriod.end | string (HH:MM:SS) | Yes for patient | Window end. Equal to start means 24 hours; before start crosses midnight. |
settings.bedTimePeriod.timezone | string | null | No | patient only. Response-only; ignored. The server resolves the zone. |
settings.oxygenSaturation | object | null | No | SpO₂ limit. Left out or null keeps what is stored. |
settings.oxygenSaturation.mode | "organization" | "patient" | Yes, if the object is sent | organization follows the home: send no other key. patient is the resident's own limit. |
settings.oxygenSaturation.enabled | boolean | Yes for patient | Whether the limit alerts. |
settings.oxygenSaturation.min, settings.oxygenSaturation.max | number | null | Yes for patient | Bounds in percent, 50 to 100 and inside the home's range (default 70 to 100). null for no bound on that side. An enabled limit needs at least one bound, and min must be below max when both are set. |
settings.pulseRate | object | null | No | Pulse-rate limit. Same fields as settings.oxygenSaturation; bounds 20 to 300 and inside the home's range (default 40 to 120). |
settings.perfusionIndex | object | null | No | Perfusion-index limit. Same fields as settings.oxygenSaturation; bounds 0 to 20 and inside the home's range (default 0 to 20). |
settings.bodyTemperature | object | null | No | Body-temperature limit. Same fields as settings.oxygenSaturation; bounds 30 to 45 and inside the home's range (default 34 to 42), in °C. |
Behaviour
mode: "organization"follows the home; send no other values.mode: "patient"is an individual limit: heart rate and respiratory rate takeenabled,minandmax; out of bed takesenabledandmaxDurationSeconds, plus an optionaldeviationPercentthat alerts sooner when an exit runs that far past the resident's usual one (it only ever brings the limit forward:maxDurationSecondsstays the limit when there is no baseline yet).mode: "baseline"(heart rate and respiratory rate only) alerts relative to the resident's own baseline, withenabledanddeviationPercent.settings.bedTimePeriod:mode: "organization"(follow the home's window) ormode: "patient"withstartandend.- Arming a baseline limit needs a baseline average on record for the resident. Without one the save answers 424. A baseline limit sent back unchanged is not re-checked, so an unrelated save still works after the average has aged out.
- A resident's own enabled limits, the four vitals included, must sit inside the home's
ranges(see Get current org threshold (v2)). One outside answers 422 naming the field, and nothing is saved. A limit sent back unchanged is not re-checked, so a home that narrows its range does not block a resident's other edits. Set ranges with Update org threshold (v2). settings.oxygenSaturation,pulseRate,perfusionIndex,bodyTemperature(each optional):{ "mode": "organization" }follows the home;{ "mode": "patient", ... }is the resident's own limit, either boundnullfor a one-sided one. Leaving one out, or sendingnull, keeps what is stored. Following a home that does not watch the vital removes the resident's rule in Signals.enabled: falsewith the values still sent stores the limit switched off and keeps its values.- Heart-rate, respiratory-rate and vital limits are evaluated by Signals. A save that changes one waits for Signals to confirm it. If Signals does not confirm, the route answers 502 with
signalsSync: { "status": "pending", "reason": ... }. The new limits are already stored by then; send the save again to retry, even if nothing differs from what is stored. An out-of-bed or bed-time save does not wait for Signals and still succeeds while Signals is unreachable. - While a save of the home's limits (Update org threshold (v2)) is running, this save answers 409 and nothing is changed: try again once it is done.
- If the resident's stored out-of-bed band cannot be read while
settings.outOfBedispatientwith nodeviationPercent, the save answers 502 and nothing is saved: a failed read is not "no band". timezonenever travels up on a PUT body.- Change-history entries are written for each changed metric.
- The response is the resident's resolved thresholds after the save, in the shape of Get patient effective threshold (v2).
signalsSyncis{ "status": "ok" }when Signals confirmed a changed medical limit,{ "status": "pending", "reason": "metric-disarmed:..." }when Signals confirmed it but a metric shown as armed was sent as removed, andnullwhen the save claimed nothing Signals evaluates.
Example
curl -X PUT 'https://api.sandbox.ovok.com/v2/slim/threshold/patient/3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b21' \
-H "Authorization: Bearer ${OVOK_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"settings": {
"heartRate": { "mode": "patient", "enabled": true, "min": 45, "max": 110 },
"respiratoryRate": { "mode": "organization" },
"outOfBed": { "mode": "patient", "enabled": true, "maxDurationSeconds": 900, "deviationPercent": 50 },
"bedTimePeriod": { "mode": "organization" }
}
}'
Successful response
200 — Patient v2 threshold updated successfully.
{
"id": null,
"patientId": "3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b21",
"projectId": "3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b11",
"updatedAt": "2026-10-09T08:30:00.000Z",
"settings": {
"heartRate": { "mode": "patient", "enabled": true, "min": 45, "max": 110 },
"respiratoryRate": { "mode": "organization", "enabled": true, "min": 8, "max": 25 },
"outOfBed": {
"mode": "patient",
"enabled": true,
"maxDurationSeconds": 900,
"deviationPercent": 50,
"baselineAverage": 312,
"effectiveMaxDurationSeconds": 420
},
"bedTimePeriod": { "mode": "organization", "start": "19:00:00", "end": "09:00:00", "timezone": "Europe/Berlin" },
"oxygenSaturation": { "mode": "organization", "enabled": true, "min": 92, "max": null },
"pulseRate": null,
"perfusionIndex": null,
"bodyTemperature": null
},
"ranges": {
"heartRate": { "min": 40, "max": 120 },
"respiratoryRate": { "min": 4, "max": 60 },
"oxygenSaturation": { "min": 70, "max": 100 },
"pulseRate": { "min": 40, "max": 120 },
"perfusionIndex": { "min": 0, "max": 20 },
"bodyTemperature": { "min": 34, "max": 42 }
},
"signalsSync": { "status": "ok" }
}
| Field | Type | Description |
|---|---|---|
id | string | null | Always null today. |
patientId | string (uuid) | The resident. |
projectId | string | The caller's project. |
updatedAt | string (ISO 8601 date-time) | null | When the home's thresholds were last saved. It does not move when only the resident's own limits are saved. |
settings | object | The limits in effect for the resident after the save. |
settings.heartRate | object | Heart-rate limit. Either the absolute shape or the baseline shape below. |
settings.heartRate.mode | "organization" | "patient" | "baseline" | organization follows the home. patient is the resident's own absolute band. baseline is the resident's own baseline band. |
settings.heartRate.enabled | boolean | Whether the limit alerts. |
settings.heartRate.min | number | Lower bound in beats per minute. Absolute shape only. |
settings.heartRate.max | number | Upper bound in beats per minute. Absolute shape only. |
settings.heartRate.deviationPercent | number | Baseline shape only: the symmetric deviation from the resident's baseline, in percent. |
settings.heartRate.baselineAverage | number | null | Baseline shape only: the resident's baseline average, or null when none is on record. |
settings.heartRate.effectiveBand | object | null | Baseline shape only: the band in effect, or null without an average. |
settings.heartRate.effectiveBand.min | number | Lower bound of the band in effect. |
settings.heartRate.effectiveBand.max | number | Upper bound of the band in effect. |
settings.respiratoryRate | object | Respiratory-rate limit. Same fields as settings.heartRate, in breaths per minute. |
settings.outOfBed | object | Out-of-bed limit. |
settings.outOfBed.mode | "organization" | "patient" | Whether the limit follows the home or is the resident's own. |
settings.outOfBed.enabled | boolean | Whether out-of-bed alerting is on. |
settings.outOfBed.maxDurationSeconds | integer | The longest exit allowed, in seconds. |
settings.outOfBed.deviationPercent | integer | null | The relative band, in percent. null when off. |
settings.outOfBed.baselineAverage | number | null | The resident's usual exit length in seconds. null with the band off, on too little data, or when it could not be read. |
settings.outOfBed.effectiveMaxDurationSeconds | number | null | The limit the evaluator applies to this resident, in seconds. null when out-of-bed alerting is off. |
settings.bedTimePeriod | object | The window in which out-of-bed is checked. |
settings.bedTimePeriod.mode | "organization" | "patient" | Whether the window is the home's or the resident's own. |
settings.bedTimePeriod.start | string (HH:MM:SS) | Window start, wall-clock time in timezone. |
settings.bedTimePeriod.end | string (HH:MM:SS) | Window end. |
settings.bedTimePeriod.timezone | string | null | IANA time zone the window is read in, resolved by the server. |
settings.oxygenSaturation | object | null | SpO₂ limit. null when neither the home nor the resident watches the vital. |
settings.oxygenSaturation.mode | "organization" | "patient" | Whether the limit follows the home or is the resident's own. |
settings.oxygenSaturation.enabled | boolean | Whether the limit alerts. |
settings.oxygenSaturation.min | number | null | Lower bound, or null for none. |
settings.oxygenSaturation.max | number | null | Upper bound, or null for none. |
settings.pulseRate | object | null | Pulse-rate limit. Same fields as settings.oxygenSaturation. |
settings.perfusionIndex | object | null | Perfusion-index limit. Same fields as settings.oxygenSaturation. |
settings.bodyTemperature | object | null | Body-temperature limit. Same fields as settings.oxygenSaturation. |
ranges | object | The home's range for each vital's limits. Always present. |
ranges.heartRate | object | { min, max }, both number. Same shape for ranges.respiratoryRate, ranges.oxygenSaturation, ranges.pulseRate, ranges.perfusionIndex and ranges.bodyTemperature. |
signalsSync | object | null | null when the save claimed nothing Signals evaluates. |
signalsSync.status | "ok" | "pending" | ok: Signals confirmed the changed limit. pending: Signals confirmed it, but a metric shown as armed was sent to Signals as removed. |
signalsSync.reason | string | Present when status is pending: metric-disarmed: followed by the affected codes. |
Errors
| Status | Meaning |
|---|---|
400 | Your session has no project. |
401 | Bearer token is missing, invalid or expired. |
403 | You are not a practitioner, admin or System Owner, you lack Goal:update, or the patient carries no project. |
404 | No patient with this id exists, or the patient is in another project. |
409 | A save of the home's limits is running. Nothing was changed; try again once it is done. |
422 | The body fails validation, or a limit is outside the home's ranges. The body lists each problem as a path and message. |
424 | The body arms a baseline band for heart or respiratory rate with no stored average for this resident. The body names them in missing. |
502 | The home's thresholds or the resident's stored out-of-bed band could not be read (nothing was saved), or the save changes a heart-rate, respiratory-rate or vital limit and Signals did not confirm it (the new limits are stored; the body has signalsSync: { "status": "pending", "reason": ... }; send the save again). |
503 | The access policy could not be read right now. Retry. |