---
title: "Escalation Policies — API v1 & MCP | Nodea"
description: "REST API v1 and MCP tooling reference for Nodea escalation policies: escalation steps (position, delay_minutes), the XOR group/integration rule, alerting scopes, plus curl and JSON-RPC examples."
url: "https://nodea.io/en/api-docs/escalation-policies"
lang: "en"
updated: "2026-07-16"
---

# Escalation Policies — API v1 & MCP | Nodea

> Escalation policies decide who gets notified, and after how long, during an incident. Learn the REST API v1 endpoints, the step object shape (XOR rule), error codes, and the full set of 5 MCP tools.

An escalation policy is an ordered list of **steps** that decide who gets notified, and after what delay, during an incident. Each step targets a single notification route — a notification group or an integration — plus a delay counted from incident open. This document covers the REST **API v1** operations and the full set of **MCP** tools for managing policies.

All endpoints live under the base path https://app.nodea.io/api/v1. Authenticate with a Passport personal access token in the Authorization: Bearer \<token\> header. The throttle:api-v1 limit of 240 requests per minute applies. Escalation policies require the **Websites** module (Pennant flag websites) — the routes ride the same flag as website monitoring, they do not have a dedicated one.

Access is owner-scoped: a foreign or unknown id always returns 404 (not 403), so the API never confirms that another tenant's resource exists. OAuth scopes: reading requires alerting:read, writing alerting:write, deleting alerting:delete.

## API v1

MethodPathScopeDescriptionGET/escalation-policiesalerting:readList the owner's policies (pagination, search, sorting).GET/escalation-policies/{escalationPolicy}alerting:readA single policy with its steps in position order.POST/escalation-policiesalerting:writeCreate a policy with a list of steps (returns 201).PATCH / PUT/escalation-policies/{escalationPolicy}alerting:writeUpdate the name and/or the whole step chain.DELETE/escalation-policies/{escalationPolicy}alerting:deletePermanently delete a policy and its steps (returns 204).

### The step object (steps\[\]) and the XOR rule

The steps field is an array of steps. Each step carries:

- delay\_minutes — integer 0–1440. The delay counted from **incident open** (not from the previous step) after which this step fires. A value of 0 means notify immediately.
- notification\_group\_id — **either** the id of one of your notification groups (fans out to every integration in the group).
- integration\_id — **or** the id of a single one of your integrations.

**The XOR rule:** each step must target **exactly one** of the two — notification\_group\_id OR integration\_id. Two validation layers enforce this:

- The *neither target* case (both omitted) — a required\_without rule on both fields returns 422.
- The *both targets* case — an additional withValidator hook adds an error to the steps.{index} key and returns 422 with a message that a step must target one route, not both.

Every step target must belong to the token user. Via a Rule::exists(...)-\>where('user\_id', ...) constraint, a foreign group or integration id also returns 422 (not 404) — so an escalation can never route through another tenant's credentials.

### Position and ordering

The position field is not accepted in the request — the **array order** of steps becomes the 0-based position (first element = 0, second = 1, …). That order defines the escalation chain. In the response, steps are always returned sorted by position.

### Response shape

The EscalationPolicyResource returns a policy with nested steps:

{ "data": { "id": 42, "name": "Critical production API", "steps": \[ { "id": 101, "position": 0, "delay\_minutes": 0, "notification\_group\_id": 7, "integration\_id": null }, { "id": 102, "position": 1, "delay\_minutes": 15, "notification\_group\_id": null, "integration\_id": 3 } \], "steps\_count": 2, "created\_at": "2026-07-15T09:00:00+00:00", "updated\_at": "2026-07-15T09:00:00+00:00" } }

In each step exactly one of notification\_group\_id / integration\_id is populated, the other is null. The steps\_count field appears when the controller counted the relation.

### GET /escalation-policies — list parameters

Supported query parameters: search (name substring, max 255), sort (whitelist: name, created\_at), direction (asc / desc), per\_page (1–100, default 20), page. Default sort: alphabetical by name.

curl -s "https://app.nodea.io/api/v1/escalation-policies?search=API&sort=name&per\_page=20" \\ -H "Authorization: Bearer $TOKEN" \\ -H "Accept: application/json"

### POST /escalation-policies — create with steps

curl -s -X POST "https://app.nodea.io/api/v1/escalation-policies" \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -H "Accept: application/json" \\ -d '{ "name": "Critical production API", "steps": \[ { "delay\_minutes": 0, "notification\_group\_id": 7 }, { "delay\_minutes": 15, "integration\_id": 3 } \] }'

The first step (position 0) fans out immediately to group 7; the second (position 1) hits integration 3 after 15 minutes. Response: 201 Created with the full resource.

### PATCH /escalation-policies/{id} — update

Updates are partial: name and steps are optional. The name only changes when the name key is sent. The steps list has **replace-all** semantics — the controller deletes the existing steps and recreates them from the submitted array in its order, but **only** when the steps key was present. Omitting steps keeps the existing chain.

# Rename only (steps unchanged) curl -s -X PATCH "https://app.nodea.io/api/v1/escalation-policies/42" \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "name": "Critical API — renamed" }'

### DELETE /escalation-policies/{id}

Permanently deletes the policy and its steps, returning 204 No Content. Websites pinned to the policy do not block the delete — their websites.escalation\_policy\_id key is nulled by the database (nullOnDelete) and they fall back to the default "notify all channels on first failure" behaviour.

curl -s -X DELETE "https://app.nodea.io/api/v1/escalation-policies/42" \\ -H "Authorization: Bearer $TOKEN"

### Error codes

CodeMeaning401Missing or invalid Bearer token.403Token without the required scope (alerting:read / write / delete) or the websites module disabled.404The policy does not exist or belongs to another user.422Validation error: missing name/steps, delay\_minutes out of 0–1440, XOR violation (neither or both targets), or a target outside your own groups/integrations.

## MCP

The same operations are exposed by 5 MCP tools. They enforce identical scopes and require the websites module. In the tools, policy ids are passed as strings. The delete tool is marked *destructive* and requires an explicit confirm: true.

NameScopeModeDescriptionescalation-policies-listalerting:readread-onlyPaginated list of policies with steps; parameters search, page, per\_page (max 50).escalation-policy-getalerting:readread-onlyA single policy with steps by id; adds group and integration names.escalation-policy-createalerting:writewriteCreate a policy from name and steps (list order = escalation order).escalation-policy-updatealerting:writewrite (idempotent)Update name and/or steps; a provided steps list replaces the whole chain.escalation-policy-deletealerting:deletedestructivePermanent delete; requires confirm: true.

### Passing steps

In escalation-policy-create and escalation-policy-update, the steps parameter is an array of objects with delay\_minutes (0–1440), notification\_group\_id, and integration\_id — with the same XOR rule (exactly one target per step). The element order becomes position. Targets outside your own groups/integrations are rejected (a single foreign id aborts the whole operation).

### tools/call — create

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "escalation-policy-create", "arguments": { "name": "Critical production API", "steps": \[ { "delay\_minutes": 0, "notification\_group\_id": 7 }, { "delay\_minutes": 15, "integration\_id": 3 } \] } } }

### tools/call — delete (with confirmation)

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "escalation-policy-delete", "arguments": { "id": "42", "confirm": true } } }

Without confirm: true the tool returns the error "Deletion requires confirm: true." and deletes nothing. On success it returns { "deleted": true, "id": "42" }. The tools return structured data — the policy with its steps in position order, together with group names (notification\_group\_name) and integration names (integration\_name).

## Related

- [Notification Groups — API v1 and MCP Tools | Nodea](https://nodea.io/en/api-docs/notification-groups)
- [Notification Integrations — API v1 and MCP | Nodea](https://nodea.io/en/api-docs/integrations)

---

Languages: [pl](https://nodea.pl/pl/dokumentacja-api/escalation-policies.md)  
Site index: [llms.txt](https://nodea.io/en/llms.txt)
