---
title: "Update a routing rule (v1.126)"
method: PATCH
path: "/agent/v1/notifications/rules/{rule_id}"
tags: ["Agent"]
---

# Update a routing rule (v1.126)

`PATCH /agent/v1/notifications/rules/{rule_id}`

## Request

- Base URL: `https://api.run402.com`
- URL: `https://api.run402.com/agent/v1/notifications/rules/{rule_id}`
- Auth: API key in header `SIGN-IN-WITH-X`

operator_passkey assurance. PATCH semantics: only fields PRESENT in the body are touched — {"project_id": null} clears the filter to wildcard, while omitting project_id leaves it unchanged. Same validation as create for any field supplied, plus enabled.

## Path parameters

- `rule_id` string, uuid, required

## Headers

- `SIGN-IN-WITH-X` string, required

## Request body

- object: Every field is optional; only fields present in the body are changed.
  - `project_id` string, nullable
  - `source` 'app' | 'platform', nullable
  - `event_types` string[], nullable
  - `classes` string[], nullable
  - `min_severity` 'info' | 'notice' | 'warn' | 'critical': Present → replaces the stored minimum (400 `INVALID_MIN_SEVERITY` outside the vocabulary). Absent → unchanged; an update never re-derives the create-time default.
  - `group_by` string[], nullable: Present → replaces (null clears). The merged rule must keep a window under a group: 400 INVALID_WINDOW_SECONDS otherwise.
  - `window_seconds` integer, nullable: Present → replaces (null returns the rule to per-event delivery; a pending window is discarded unsent, the feed keeps every event).
  - `escalation_level` integer: Escalation rules only. A rule keeps its channel: channel is not patchable, and the other channel's field is 400 INVALID_RULE_FIELD.
  - `telegram_binding_id` string, uuid
  - `enabled` boolean

## Response `200`

Updated rule

- NotificationRoutingRule: One rule = one match -> one destination (notification-channel-routing-telegram, v1.126). Every match dimension is optional; absent = wildcard, ANDed when present. No rules = no Telegram traffic; email/webhook are governed by preferences, untouched by this capability.
  - `id` string, uuid
  - `recipient_email` string
  - `project_id` string, nullable: Wildcard when null.
  - `source` 'app' | 'platform', nullable: Wildcard when null.
  - `event_types` string[], nullable: Wildcard when null. An explicit [] matches NO event_type — a real, if unsatisfiable, filter — distinct from omitting the field.
  - `classes` string[], nullable: Wildcard when null; same [] semantics as event_types.
  - `min_severity` 'info' | 'notice' | 'warn' | 'critical': event-attention-rules: only events at or above this severity match. Never null: a rule created without one stores `notice` when it names no event_types (routine traffic stays in the feed) and `info` when it names some (naming a type opts in to it). Rules that existed before severity shipped read `info` and deliver exactly what they did.
  - `group_by` string[], nullable: event-attention-rules: 1 to 3 top-level payload keys (^[A-Za-z_][A-Za-z0-9_]{0,63}$) whose values group a window's events; needs window_seconds. A rule holds at most 200 distinct keys per window; further keys are counted under (other), so varying a payload value multiplies neither messages nor stored state.
  - `window_seconds` integer, nullable: event-attention-rules: reduce a run of events to ONE message per closed wall-clock window (aligned to multiples of window_seconds), with one line per group key ordered by count, ten lines then a summary, plus the total and up to three bounded samples. 60 to 86400. Null delivers per event.
  - `next_window_closes_at` string, date-time: Windowed rules only: when the current window closes — the earliest a message can arrive. Computed at read.
  - `escalation_level` integer, nullable: event-attention-rules: set exactly for channel escalation — the ladder level a match raises at (the lowest staffed level at or above it).
  - `escalation_refused_count` integer: Escalation rules: raises the bounds refused (20 per rule per UTC day; 20 open per organization). 0 otherwise.
  - `last_escalation_refused_at` string, date-time, nullable
  - `channel` 'telegram' | 'escalation': event-attention-rules: `telegram` sends to telegram_binding_id; `escalation` raises on the organization's escalation ladder at escalation_level (mandatory delivery to the contacts there, one-tap ack, deadman climb), and one open escalation per rule: a repeat attaches. Was generic on purpose (widening to email/webhook rules later is a CHECK-constraint migration, not a reshape); v1 only ever writes 'telegram'.
  - `telegram_binding_id` string, uuid
  - `enabled` boolean
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `400`: Malformed input, or no mutable fields present in the patch (NO_FIELDS)
- `403`: operator_passkey assurance not met (FORBIDDEN)
- `404`: rule_id not found/not owned, or a referenced telegram_binding_id/project_id is missing, foreign, or unusable (RULE_REF_NOT_FOUND) — authorize-before-reveal

---

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