---
title: Update patient threshold (v2)
sidebar_label: Update patient threshold (v2)
description: "Update patient threshold (v2) using Ovok’s Slim API."
---

# Update patient threshold (v2)


| Method | Path |
| --- | --- |
| `PUT` | `/v2/slim/threshold/patient/:patientId` |

[Authentication](/authentication) · [Access policies](/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}`](/slim-apis/threshold/update-patient-threshold). 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 take `enabled`, `min` and `max`; out of bed takes `enabled` and `maxDurationSeconds`, plus an optional `deviationPercent` that alerts sooner when an exit runs that far past the resident's usual one (it only ever brings the limit forward: `maxDurationSeconds` stays the limit when there is no baseline yet). `mode: "baseline"` (heart rate and respiratory rate only) alerts relative to the resident's own baseline, with `enabled` and `deviationPercent`.
- `settings.bedTimePeriod`: `mode: "organization"` (follow the home's window) or `mode: "patient"` with `start` and `end`.
- 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)](/slim-apis/threshold/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)](/slim-apis/threshold/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 bound `null` for a one-sided one. Leaving one out, or sending `null`, keeps what is stored. Following a home that does not watch the vital removes the resident's rule in Signals.
- `enabled: false` with 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)](/slim-apis/threshold/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.outOfBed` is `patient` with no `deviationPercent`, the save answers 502 and nothing is saved: a failed read is not "no band".
- `timezone` never 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)](/slim-apis/threshold/get-patient-effective-threshold-v2). `signalsSync` is `{ "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, and `null` when the save claimed nothing Signals evaluates.

## Example

```bash
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.

```json
{
  "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. |
