Nodea — logo

Escalation Policies — API v1 & MCP | Nodea

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

MethodPathScopeDescription
GET/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 01440. 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_ideither the id of one of your notification groups (fans out to every integration in the group).
  • integration_idor 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

CodeMeaning
401Missing 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.

NameScopeModeDescription
escalation-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).