---
title: "Create a notification rule for a group"
method: POST
path: "/api/v1/groups/{group}/notification-settings"
tags: ["Notification settings"]
---

# Create a notification rule for a group

`POST /api/v1/groups/{group}/notification-settings`

## Request

- Base URL: `https://wardenpoint.com` (the document declares no server; this is the origin it was published from)
- URL: `https://wardenpoint.com/api/v1/groups/{group}/notification-settings`
- Auth: API key in header `X-API-Key`

Adds a rule for one priority. A group holds at most one rule per priority; repeating the call for a priority that already has one fails with 422 rather than creating a duplicate — update the existing rule instead. Set override_recipient_settings to make the rule beat the personal rules of every member. `priority` is the one required field here; on the update endpoints it is optional.

## Path parameters

- `group` string, uuid, required

## Request body

- GroupNotificationSettingCreateRequest: Sparse, exactly as it reads: only the fields you send are written and everything omitted keeps its stored value — including `priority`. Nothing in this body is required, because the rule being written is already named by its UUID in the path. Creating a rule is the one case that needs more, and it has its own schema: `GroupNotificationSettingCreateRequest`. The four limits below — the quiet window, the schedule grid, the gap and the daily cap — mean exactly what they mean on a recipient rule and are enforced by the same code; they simply apply to whichever member this rule governs. Their hours are read on the platform clock, `Europe/Kyiv`. Defaults, though, are **not** the same as the recipient rule's in two places, and each field says so where it differs. **Being over a limit does not postpone an alert indefinitely, and for two of the four it does not postpone it at all.** A quiet window and a `min_interval_minutes` gap name the moment they end, so the alert waits and is delivered then — provided that moment falls inside the hour a send is retried for. Beyond that hour, and for the daily cap (which ends at midnight, almost always further off than an hour), the alert ends `cancelled`: it carries `blocked_reason` and the time it would have become allowed, and nobody re-sends it. An hour outside `schedule_grid` names no moment to wait for at all, so that one does not wait even briefly — it ends `failed`. Read the two statuses as they are meant: `cancelled` is the platform declining to page on your instructions, `failed` is a delivery that did not happen. **Only the first page is subject to any of this.** Escalation steps, the owner fallback and test alerts bypass all four checks by design — an escalation that went quiet during quiet hours would be an escalation chain running with nobody at the end of it. So a quiet window mutes the opening page and nothing that follows it.
  - `priority` 'low' | 'normal' | 'high' | 'critical': One rule per priority per group. Required when creating; on an update it is an ordinary field — send it to move this rule to another priority, omit it to leave the priority alone. Either way, landing on a priority the group already has a rule for fails with 422.
  - `override_recipient_settings` boolean: True makes this rule beat the personal rules of every member — the one way to say "during this incident everybody is alerted like this".
  - `escalation_rules` EscalationStep[], nullable: Inline escalation steps, replaced wholesale. An empty list does not disable escalation — the account-wide default steps still apply. **This is also where you say who the first page of a group alert wakes** — the `initial` step's target is the only thing that decides it. Give that step `schedule_targets` and the first page goes to whoever is on duty on those rotations, one person rather than one page per member. Give it `target_recipient_uuid` and it goes to that one person. Give it neither and it goes to **every member of the group at once**; that is the deliberate "all hands on deck" shape, not an unfinished rule, and it is what a rule with no steps does as well. A named target can still resolve to nobody at delivery time — no shift covers this moment, or the person on duty was deactivated. The step's `fallback_recipient_uuid` is paged then; with no fallback set, the alert reaches every member rather than being dropped. That substitution is never silent, but it is reported to the operator rather than back through this API: the platform logs a warning naming the reason (`nobody_on_call`) and counts it in `wardenpoint_group_initial_target_unmet_total`. Do not expect a field here to tell you the first page went wide.
    - `trigger` 'initial' | 'on_fail' | 'timeout': When the step runs. `initial` — the first dispatch. `on_fail` — the preceding dispatch could not be delivered at all (not a timeout: delivery failed). `timeout` — nobody acknowledged within delay_minutes; this is the step that escalates.
    - `action` 'send' | 'retry' | 'notify_manager': `send` — deliver over this step's channel/provider. `retry` — repeat the previous dispatch to the same target. `notify_manager` — deliver to somebody else; requires target_recipient_uuid.
    - `delay_minutes` union: How long the step waits before it runs, counted from the moment the chain reached it. On a timeout step this is therefore the acknowledgement window of the previous step. 0 means immediately, and is honoured literally — a timeout step at 0 runs as soon as the preceding dispatch is out, which is how you reach everybody at once. The literal string "adaptive" derives the delay from the alert priority and from how fast this company usually acknowledges; the `bounds_minutes` and `priority_defaults_minutes` the vocabulary reports under `adaptive` constrain that derived value only and never the number you send here.
      - integer
      - 'adaptive'
    - `channel` 'voice_call' | 'text_message' | 'voice_message', nullable: `text_message` — a written message. `voice_message` — a pre-rendered voice note inside a messenger. `voice_call` — a call that rings the person. Omit and the step delivers as a `text_message`, with the provider picked from the contacts that recipient can actually receive on — the same thing `provider: auto` does. Omitting it does not mean "no delivery": a step always names a channel by the time it runs.
    - `provider` 'telegram' | 'telegram_bot' | 'platform_asterisk' | 'custom_asterisk' | 'whatsapp' | 'viber' | 'email' | 'sms' | 'slack' | 'msteams' | 'auto', nullable: Who delivers it: telegram, telegram_bot, platform_asterisk, custom_asterisk, email, sms, whatsapp, viber, slack, msteams — or "auto" to pick the first provider the recipient actually has a contact for. A slug outside this list is refused, and so is a provider that cannot carry the step's channel — email cannot place a voice_call. Which providers this company may use depends on its plan; call GET /escalation-policies/vocabulary for the live list.
    - `ack_on` 'button' | 'read' | 'delivered', nullable: What counts as acknowledgement and therefore stops the chain. `button` — the person pressed acknowledge (the only mode that proves a human saw it). `read` — the provider reported a read receipt. `delivered` — the provider reported delivery; for voice_call it means the call was answered. Not every provider supports every mode — see ack_capabilities in the vocabulary. A mode the provider cannot report never acknowledges.
    - `target_recipient_uuid` string, uuid, nullable: Deliver this step to a specific person instead of the alert recipient. Required when action is notify_manager.
    - `schedule_targets` object[], nullable: Priority-ordered on-call targets. The first entry that resolves to somebody wins.
      - `schedule_uuid` string, uuid: An active schedule of this company.
      - `role` 'primary' | 'secondary' | 'manager': Which slot of the rotation to page. These are parallel duty tiers, not an order of escalation: `secondary` is the person covering alongside `primary` right now, NOT the person who takes over from them. For "the next one" use `occupant`.
      - `occupant` 'current' | 'next', nullable: Which person of that rotation this entry means. `current` — the default, and what every step written before this field existed does: whoever is on duty in that role right now. `next` — whoever takes that same role at the next handoff of that same rotation. This is how you say "if nobody answers, wake the next one" without inventing a second rotation shifted by one person: two rotations agree only until somebody regenerates one of them with a different line-up, and nothing in this API would report the drift. `next` is counted from the moment the alert was raised, not from the moment the step runs — a chain can cross a handoff, and an alert at 08:55 whose second rung fires at 09:05 must reach the person who came on at 09:00, not the one after them. An override on the next shift wins, so `next` names whoever will actually answer. It can legitimately resolve to nobody: when the next person is the one already on duty (a single-member rotation, or an override handing the shift back to them) it does not page them twice — escalating to the same person is what `retry` is for — and when the rotation has no shift after this one it has nobody to name. Either way the step moves on to its next candidate and then to fallback_recipient_uuid, and the outcome is reported to the operator, not through this API: a warning naming the reason (`same_as_current`, `no_upcoming_shift`) and the `wardenpoint_next_on_call_unmet_total` counter.
    - `fallback_recipient_uuid` string, uuid, nullable: Used when no schedule target resolved to anybody.
    - `target_priority` 'schedule_first', nullable: Send "schedule_first" to consult the rotations before target_recipient_uuid. Omit for the default order: specific person, then rotations, then fallback.
    - `audio_source` 'tts' | 'file', nullable: For voice channels: `tts` synthesises the alert text, `file` plays a stored recording.
    - `audio_file_uuid` string, uuid, nullable: The recording to play. Required when audio_source is file.
    - `tts_engine` string, nullable: Speech engine override. Premium engines need a plan that includes them.
    - `tts_text_override` string, nullable: Say this instead of the alert text.
    - `ai_processing` boolean, nullable: Let the AI rewrite the alert into speakable prose before synthesis. Requires a plan with custom TTS.
  - `escalation_policy_uuid` string, uuid, nullable: Point this rule at a reusable policy. Send null to detach and fall back to the inline steps. **Sending both this and escalation_rules is allowed, and the policy wins** — as long as it has at least one step of its own. Delivery asks the policy first and only reads the inline steps when the policy has nothing to say (which also covers the attached-but-empty policy, so a rule can never end up with no ladder at all). The inline steps are not erased by attaching a policy; they wait, and come back the moment you detach it. Identical on the recipient rule — the two are resolved by the same code, and must not be described as if they differed.
  - `schedule_grid` object, nullable: Allowed delivery hours as {weekday "0".."6": [hours 0..23]}, weekday 0 being Sunday. A complete 7x24 grid is stored as "round the clock", that is, as null, and reads back as null rather than as the grid you sent. Null (the default) allows every hour. Of the four limits in this schema this is the unforgiving one: an alert arriving outside the grid ends `failed` straight away, because unlike a quiet window an hour-grid names no moment to wait for. See the note on the schema itself.
  - `quiet_hours_start` integer, nullable: Hour the quiet window opens, on the platform clock. Null (the default) means no quiet hours.
  - `quiet_hours_end` integer, nullable: Hour the quiet window closes. Required whenever quiet_hours_start is sent. May be smaller than the start — the window then crosses midnight.
  - `bypass_quiet_hours_critical` boolean: Let `critical` alerts through the quiet window. Only `critical` — the other three priorities wait however this is set. Default true.
  - `min_interval_minutes` integer: Smallest gap between two alerts to a member this rule governs, in minutes. Default 0, meaning no gap. Measured per person against their last completed delivery on any priority — the limit belongs to the member, not to the group, so it does not thin out a fan-out to twelve people into one page.
  - `max_daily_notifications` integer, nullable: Cap on deliveries per member per calendar day, counted the same way: completed deliveries only, any priority, test alerts excluded, day rolling over at midnight on the platform clock. Null (the default here) or 0 means no cap.

## Response `201`

The created rule.

- object
  - `data` GroupNotificationSetting: The rule for one group on one priority. Unlike a recipient rule it has its own uuid. **Who the FIRST page reaches is decided by the target of the `initial` step, and by nothing else.** A step naming `schedule_targets` pages whoever is on duty on those rotations at that moment; a step naming `target_recipient_uuid` pages that one person; **a step naming neither pages every member of the group at once** — and so does a group with no rule for this priority. There is no separate group-wide switch to look for: the step's own target is the whole statement, and "nobody named" is a statement too. Only the first page works this way; the timeout steps that escalate afterwards pick their targets as documented on EscalationStep. **The priority an observability alert arrives on is decided by the routing rule's action, not by the alert severity**: `escalate` produces `critical`, every other notifying action produces `normal`. A group rule on `critical` therefore governs alerts an `escalate` rule matched — not alerts whose severity happened to be critical. See the AccumulationRule schema.
    - `uuid` string, uuid
    - `priority` 'low' | 'normal' | 'high' | 'critical'
    - `override_recipient_settings` boolean: True makes this rule beat the personal rules of every member. False (the default) makes it a fallback for members who have no rule of their own.
    - `escalation_policy_uuid` string, uuid, nullable: When set, the policy supplies the steps and the inline ones below are ignored — unless the policy has no steps of its own, in which case the inline ones run after all. The policy's steps are not inlined here; read the policy by this UUID to see them.
    - `steps` EscalationStep[]: The steps stored **on this rule** — not necessarily the ones that would run. A rule that delegates to a policy commonly reads back as `[]`, which is not the same as "this group has no escalation": the policy governs, and even a rule with genuinely no steps falls back to the account-wide default ladder.
      - `trigger` 'initial' | 'on_fail' | 'timeout': When the step runs. `initial` — the first dispatch. `on_fail` — the preceding dispatch could not be delivered at all (not a timeout: delivery failed). `timeout` — nobody acknowledged within delay_minutes; this is the step that escalates.
      - `action` 'send' | 'retry' | 'notify_manager': `send` — deliver over this step's channel/provider. `retry` — repeat the previous dispatch to the same target. `notify_manager` — deliver to somebody else; requires target_recipient_uuid.
      - `delay_minutes` union: How long the step waits before it runs, counted from the moment the chain reached it. On a timeout step this is therefore the acknowledgement window of the previous step. 0 means immediately, and is honoured literally — a timeout step at 0 runs as soon as the preceding dispatch is out, which is how you reach everybody at once. The literal string "adaptive" derives the delay from the alert priority and from how fast this company usually acknowledges; the `bounds_minutes` and `priority_defaults_minutes` the vocabulary reports under `adaptive` constrain that derived value only and never the number you send here.
        - integer
        - 'adaptive'
      - `channel` 'voice_call' | 'text_message' | 'voice_message', nullable: `text_message` — a written message. `voice_message` — a pre-rendered voice note inside a messenger. `voice_call` — a call that rings the person. Omit and the step delivers as a `text_message`, with the provider picked from the contacts that recipient can actually receive on — the same thing `provider: auto` does. Omitting it does not mean "no delivery": a step always names a channel by the time it runs.
      - `provider` 'telegram' | 'telegram_bot' | 'platform_asterisk' | 'custom_asterisk' | 'whatsapp' | 'viber' | 'email' | 'sms' | 'slack' | 'msteams' | 'auto', nullable: Who delivers it: telegram, telegram_bot, platform_asterisk, custom_asterisk, email, sms, whatsapp, viber, slack, msteams — or "auto" to pick the first provider the recipient actually has a contact for. A slug outside this list is refused, and so is a provider that cannot carry the step's channel — email cannot place a voice_call. Which providers this company may use depends on its plan; call GET /escalation-policies/vocabulary for the live list.
      - `ack_on` 'button' | 'read' | 'delivered', nullable: What counts as acknowledgement and therefore stops the chain. `button` — the person pressed acknowledge (the only mode that proves a human saw it). `read` — the provider reported a read receipt. `delivered` — the provider reported delivery; for voice_call it means the call was answered. Not every provider supports every mode — see ack_capabilities in the vocabulary. A mode the provider cannot report never acknowledges.
      - `target_recipient_uuid` string, uuid, nullable: Deliver this step to a specific person instead of the alert recipient. Required when action is notify_manager.
      - `schedule_targets` object[], nullable: Priority-ordered on-call targets. The first entry that resolves to somebody wins.
        - `schedule_uuid` string, uuid: An active schedule of this company.
        - `role` 'primary' | 'secondary' | 'manager': Which slot of the rotation to page. These are parallel duty tiers, not an order of escalation: `secondary` is the person covering alongside `primary` right now, NOT the person who takes over from them. For "the next one" use `occupant`.
        - `occupant` 'current' | 'next', nullable: Which person of that rotation this entry means. `current` — the default, and what every step written before this field existed does: whoever is on duty in that role right now. `next` — whoever takes that same role at the next handoff of that same rotation. This is how you say "if nobody answers, wake the next one" without inventing a second rotation shifted by one person: two rotations agree only until somebody regenerates one of them with a different line-up, and nothing in this API would report the drift. `next` is counted from the moment the alert was raised, not from the moment the step runs — a chain can cross a handoff, and an alert at 08:55 whose second rung fires at 09:05 must reach the person who came on at 09:00, not the one after them. An override on the next shift wins, so `next` names whoever will actually answer. It can legitimately resolve to nobody: when the next person is the one already on duty (a single-member rotation, or an override handing the shift back to them) it does not page them twice — escalating to the same person is what `retry` is for — and when the rotation has no shift after this one it has nobody to name. Either way the step moves on to its next candidate and then to fallback_recipient_uuid, and the outcome is reported to the operator, not through this API: a warning naming the reason (`same_as_current`, `no_upcoming_shift`) and the `wardenpoint_next_on_call_unmet_total` counter.
      - `fallback_recipient_uuid` string, uuid, nullable: Used when no schedule target resolved to anybody.
      - `target_priority` 'schedule_first', nullable: Send "schedule_first" to consult the rotations before target_recipient_uuid. Omit for the default order: specific person, then rotations, then fallback.
      - `audio_source` 'tts' | 'file', nullable: For voice channels: `tts` synthesises the alert text, `file` plays a stored recording.
      - `audio_file_uuid` string, uuid, nullable: The recording to play. Required when audio_source is file.
      - `tts_engine` string, nullable: Speech engine override. Premium engines need a plan that includes them.
      - `tts_text_override` string, nullable: Say this instead of the alert text.
      - `ai_processing` boolean, nullable: Let the AI rewrite the alert into speakable prose before synthesis. Requires a plan with custom TTS.
    - `schedule_grid` object, nullable: Allowed delivery hours as {weekday "0".."6": [hours 0..23]}, weekday 0 being Sunday. Null means round the clock — a full 7x24 grid is stored, and read back, as null.
    - `quiet_hours_start` integer, nullable: Hour the quiet window opens, on the platform clock. Null means no quiet hours.
    - `quiet_hours_end` integer, nullable: Hour it closes. Smaller than the start means the window crosses midnight.
    - `bypass_quiet_hours_critical` boolean: Whether `critical` alerts are let through the quiet window. Only `critical` is ever exempt.
    - `min_interval_minutes` integer: Smallest gap between two alerts to a member, in minutes; 0 means no gap. Held per person against their last completed delivery on any priority, so it thins repeats to one member rather than thinning a fan-out.
    - `max_daily_notifications` integer, nullable: Deliveries allowed per member per calendar day; null or 0 means no cap. Counts completed, non-test deliveries only.
    - `created_at` string, date-time
    - `updated_at` string, date-time

## Other responses

- `401`: API key missing, invalid, revoked or expired.
- `403`: API key lacks the config.write ability, or the group belongs to another company.
- `404`: No group with this UUID.
- `422`: Validation failed — the priority is missing, the group already has a rule for it, or a referenced policy, audio file, recipient or schedule belongs to another company.

---

[API](https://skmtc.dev/wardenpoint/apis/wardenpoint-api.md) · [All operations](https://skmtc.dev/wardenpoint/apis/wardenpoint-api/llms.txt) · [OpenAPI document](https://skmtc.dev/wardenpoint/apis/wardenpoint-api/revisions/259ee445728e?raw)
