Update org threshold (v2)
| Method | Path |
|---|---|
PUT | /v2/slim/threshold |
Authentication · Access policies
Saves the home's threshold configuration in the v2 shape: the alert limits every resident follows unless they have limits of their own. mode is required on every settings field; patient is not a valid mode here.
This route supersedes PUT /v1/slim/threshold. Only it can set the home's limit ranges, the optional vitals, the relative out-of-bed band and the bed-time window.
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 (admins and super admins skip this check).
Scope: The caller's project (from the token).
Request
Body
Keys not listed below are ignored at the top level and inside settings. Inside each metric object, an unlisted key answers 422, except the response-only keys marked "ignored".
| Name | Type | Required | Description |
|---|---|---|---|
settings | object | Yes | The home's limits. |
settings.heartRate | object | Yes | Heart-rate limit. |
settings.heartRate.mode | "organization" | Yes | The only accepted value. "baseline" answers 422: a home's band is absolute, and baseline limits are set per resident. |
settings.heartRate.enabled | boolean | Yes | Whether the limit alerts. |
settings.heartRate.min | number | Yes | 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 | Upper bound in beats per minute, 20 to 300 and inside the home's heart-rate range. |
settings.respiratoryRate | object | Yes | Respiratory-rate limit. Same fields as settings.heartRate. |
settings.respiratoryRate.min, settings.respiratoryRate.max | number | Yes | Bounds in breaths per minute, 4 to 150 and inside the home's respiratory-rate range (default 4 to 60). min must be below max when enabled. |
settings.outOfBed | object | Yes | Out-of-bed limit. |
settings.outOfBed.mode | "organization" | Yes | The only accepted value. |
settings.outOfBed.enabled | boolean | Yes | Whether out-of-bed alerting is on. |
settings.outOfBed.maxDurationSeconds | integer | Yes | 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 | 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. |
settings.outOfBed.baselineAverage | number | null | No | Response-only; ignored. |
settings.outOfBed.effectiveMaxDurationSeconds | number | null | No | Response-only; ignored. |
settings.bedTimePeriod | object | Yes | The window in which out-of-bed is checked. |
settings.bedTimePeriod.mode | "organization" | Yes | The only accepted value. |
settings.bedTimePeriod.start | string (HH:MM:SS) | Yes | Window start, wall-clock time in the home's time zone. |
settings.bedTimePeriod.end | string (HH:MM:SS) | Yes | Window end. Equal to start means 24 hours; before start crosses midnight. |
settings.bedTimePeriod.timezone | string | null | No | 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" | Yes, if the object is sent | The only accepted value. |
settings.oxygenSaturation.enabled | boolean | Yes, if the object is sent | Whether the limit alerts. Disabling removes the vital's rules in Signals. |
settings.oxygenSaturation.min, settings.oxygenSaturation.max | number | null | Yes, if the object is sent | 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. |
ranges | object | No | The range each vital's limits may take, for the home and every resident. A vital left out keeps its stored range, or the default. |
ranges.heartRate | object | No | { min, max }, both number, 20 to 300, min below max. |
ranges.respiratoryRate | object | No | { min, max }, 4 to 150, min below max. |
ranges.oxygenSaturation | object | No | { min, max }, 50 to 100, min below max. The home must have an oxygenSaturation setting, stored or in this body. |
ranges.pulseRate | object | No | { min, max }, 20 to 300, min below max. Needs a pulseRate setting. |
ranges.perfusionIndex | object | No | { min, max }, 0 to 20, min below max. Needs a perfusionIndex setting. |
ranges.bodyTemperature | object | No | { min, max }, 30 to 45, min below max. Needs a bodyTemperature setting. |
Behaviour
settings.heartRateandsettings.respiratoryRate:mode: "organization"withenabled,minandmax. The home's band is absolute;mode: "baseline"answers 422, because a resident without a baseline of their own would get no alarm from it. Set baseline limits per resident with Update patient threshold (v2).settings.outOfBed:mode: "organization"withenabledandmaxDurationSeconds, plus an optionaldeviationPercent. With it, every resident on the home's out-of-bed setting alerts once an exit runs that far past their own usual exit, if that is sooner thanmaxDurationSeconds. Omit it to keep the stored value.settings.bedTimePeriod:startandendasHH:MM:SS.endequal tostartmeans 24 hours;endbeforestartcrosses midnight.settings.oxygenSaturation,pulseRate,perfusionIndex,bodyTemperature(each optional): the home's default for every resident. Either bound may benullfor a one-sided limit. Omitting one, or sendingnull, keeps what is stored. Disabling one removes its rules in Signals.ranges(optional): the range each vital's limits may take. A range for SpO₂, pulse rate, perfusion index or body temperature is kept with that vital's setting, so the home must have set the vital (it may be disabled); otherwise the PUT answers 422 onranges.<vital>.- Every enabled limit must sit inside its range, counting
rangesin the same request. One outside answers 422 naming the field (for examplesettings.heartRate.max) and nothing is saved. A disabled band is not checked. - Narrowing a range does not rewrite residents whose limits fall outside it. A resident's next change to such a limit must fit; their other edits still save.
- Medical limits (heart rate, respiratory rate and the optional vitals) are evaluated by Signals. A save that changes one is all or nothing: it writes every limit it changes to each resident following the home on it, and answers only once Signals holds them for all of them. A resident with limits of their own for a vital keeps them, and a limit switched off is removed from the followers' rules only. Residents Signals does not hold yet are enrolled first. While it runs, a resident's own limits save answers 409.
- Homes with many residents take longer: the save gives up after about 25 seconds and puts back what it wrote. Putting back can take longer than the gateway allows, so the caller may get a 504 while it finishes. Read the limits (Get current org threshold (v2)) before sending the save again: until the undo is done, another save answers 409.
- If any of that fails, everything the save changed is put back and it answers an error with
saved: falseandunconfirmedResidents(the resident ids it flagged "limits not confirmed"). Nothing is stored; send the save again:- 409: another save of this home's limits is running, the home is moving to another Signals tenant, or a resident's or the home's own limits were saved while this one ran.
- 422: more than 1,000 residents follow the limits the save changes (the most one save writes).
- 502: the save ran out of time, or Signals did not take the limits for every resident. Residents Signals holds no patient for are named in
unconfirmedResidents: re-push them withPOST /signals/config/unconfirmed/repush(the same access as this save) before saving again. - 500: putting the previous limits back failed too. The residents left behind are named in
unconfirmedResidentsand flagged, andPOST /signals/config/unconfirmed/repushputs them right.
- Any other save (out of bed, the bed-time window) is stored and passed on to residents in the background.
- If the stored thresholds cannot be read, the PUT answers 502 and nothing is saved.
- A GET response can be sent back as a PUT body, with two exceptions: a heart-rate or respiratory-rate field still stored on baseline (see above), and a range the GET reports for an optional vital the home has never set.
- A change-history entry is written for a save that stands. The response is the saved configuration, with
signalsSync: { "status": "ok" }when the save changed medical limits andnullotherwise.
Example
curl -X PUT 'https://api.sandbox.ovok.com/v2/slim/threshold' \
-H "Authorization: Bearer ${OVOK_TOKEN}" \
-H 'Content-Type: application/json' \
-d '{
"settings": {
"heartRate": { "mode": "organization", "enabled": true, "min": 40, "max": 120 },
"respiratoryRate": { "mode": "organization", "enabled": true, "min": 8, "max": 25 },
"outOfBed": { "mode": "organization", "enabled": true, "maxDurationSeconds": 900, "deviationPercent": 50 },
"bedTimePeriod": { "mode": "organization", "start": "22:00:00", "end": "06:00:00" },
"oxygenSaturation": { "mode": "organization", "enabled": true, "min": 92, "max": null }
},
"ranges": { "heartRate": { "min": 40, "max": 140 } }
}'
Successful response
200 — v2 threshold updated successfully. The body is the saved configuration, in the same shape as Get current org threshold (v2).
{
"id": "3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b31",
"projectId": "3f1c2b7e-8d4a-4c1e-9b2f-6a7d5e4c3b11",
"updatedAt": "2026-10-09T09:12:44.000Z",
"settings": {
"heartRate": { "mode": "organization", "enabled": true, "min": 40, "max": 120 },
"respiratoryRate": { "mode": "organization", "enabled": true, "min": 8, "max": 25 },
"outOfBed": {
"mode": "organization",
"enabled": true,
"maxDurationSeconds": 900,
"deviationPercent": 50,
"baselineAverage": null,
"effectiveMaxDurationSeconds": null
},
"bedTimePeriod": { "mode": "organization", "start": "22:00:00", "end": "06: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": 140 },
"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 | The id of the stored threshold document. |
projectId | string | The caller's project. |
updatedAt | string (ISO 8601 date-time) | null | When this save was stored. |
settings | object | The home's limits as saved. |
settings.heartRate | object | Heart-rate limit. Either the absolute shape or, for a legacy home, the baseline shape below. |
settings.heartRate.mode | "organization" | "baseline" | organization for an absolute band. baseline only for a legacy home stored on a 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 | null | Baseline shape only. Always null: the baseline is per resident. |
settings.heartRate.effectiveBand | null | Baseline shape only. Always null: the band is per resident. |
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" | Always organization on this route. |
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 | null | Always null here: the usual exit length is per resident. |
settings.outOfBed.effectiveMaxDurationSeconds | null | Always null here: the limit in effect is per resident. |
settings.bedTimePeriod | object | The window in which out-of-bed is checked. |
settings.bedTimePeriod.mode | "organization" | Always organization on this route. |
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 the home does not watch the vital. |
settings.oxygenSaturation.mode | "organization" | Always organization on this route. |
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 range each vital's limits may take in this home, after this save. |
ranges.heartRate | object | { min, max }, both number. Same shape for ranges.respiratoryRate, ranges.oxygenSaturation, ranges.pulseRate, ranges.perfusionIndex and ranges.bodyTemperature. |
signalsSync | object | null | { "status": "ok" } when the save changed a medical limit and Signals confirmed it for every resident following the home. null when the save changed only out-of-bed or the bed-time window. |
signalsSync.status | "ok" | Present when signalsSync is not null. |
Errors
| Status | Meaning |
|---|---|
400 | Your session has no project, or the FHIR server refused the write. |
401 | Bearer token is missing, invalid or expired. |
403 | You are not a practitioner, admin or System Owner, or you lack PlanDefinition:update. |
409 | Another save of this home's limits is running (a save the gateway timed out may still be undoing), the home is moving to another Signals tenant, or a resident's or the home's own limits were saved while this one ran. Nothing was changed; read the limits, then send the save again. |
422 | The body fails validation (including mode: "baseline" on heart or respiratory rate), a limit is outside the home's ranges, ranges names an optional vital the home has no setting for, or more than 1,000 residents follow the limits it changes. The body lists each problem as a path and message. |
500 | The save failed and putting the previous limits back failed too. The body has saved: false and the residents left behind in unconfirmedResidents. |
502 | The home's stored thresholds could not be read, the save ran out of time, or Signals did not take the changed heart-rate, respiratory-rate or vital limits for every resident following the home. Nothing was changed; send the save again. |
503 | The access policy could not be read right now. Retry. |
504 | The gateway's 29 s ran out while a failed save was still being put back (a home with many residents). Read the limits before sending the save again: until that is done, another save answers 409. |