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

# Update org threshold (v2)


| Method | Path |
| --- | --- |
| `PUT` | `/v2/slim/threshold` |

[Authentication](/authentication) · [Access policies](/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`](/slim-apis/threshold/update-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.heartRate` and `settings.respiratoryRate`: `mode: "organization"` with `enabled`, `min` and `max`. 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)](/slim-apis/threshold/update-patient-threshold-v2).
- `settings.outOfBed`: `mode: "organization"` with `enabled` and `maxDurationSeconds`, plus an optional `deviationPercent`. 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 than `maxDurationSeconds`. Omit it to keep the stored value.
- `settings.bedTimePeriod`: `start` and `end` as `HH:MM:SS`. `end` equal to `start` means 24 hours; `end` before `start` crosses midnight.
- `settings.oxygenSaturation`, `pulseRate`, `perfusionIndex`, `bodyTemperature` (each optional): the home's default for every resident. Either bound may be `null` for a one-sided limit. Omitting one, or sending `null`, 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 on `ranges.<vital>`.
- Every enabled limit must sit inside its range, counting `ranges` in the same request. One outside answers 422 naming the field (for example `settings.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)](/slim-apis/threshold/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: false` and `unconfirmedResidents` (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 with `POST /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 `unconfirmedResidents` and flagged, and `POST /signals/config/unconfirmed/repush` puts 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 and `null` otherwise.

## Example

```bash
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)](/slim-apis/threshold/get-current-org-threshold-v2).

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