---
title: "Notification Integrations — API v1 and MCP | Nodea"
description: "Manage notification integrations (Slack, email, Discord, MS Teams, Google Chat, webhook, PagerDuty, OpsGenie, SMS, Telegram) via the REST API v1 and MCP server. Alerting scopes, secret masking, test notifications."
url: "https://nodea.io/en/api-docs/integrations"
lang: "en"
updated: "2026-07-16"
---

# Notification Integrations — API v1 and MCP | Nodea

> Manage Nodea notification integrations programmatically through the REST API v1 and the MCP server. Secrets (webhook URLs, API keys) are always masked in responses, and sending a masked value back on update never overwrites the stored credential.

Integrations are the **notification channels** Nodea uses to report outages of your monitored resources: Slack, email, Discord, MS Teams, Google Chat, a generic webhook, PagerDuty, OpsGenie, SMS and Telegram. Every integration belongs to a single user and stores credentials (webhook URL, API key, email address, phone number). This document covers the **REST API v1** and the **MCP server** for managing integrations programmatically. Related resources are [notification groups](https://nodea.io/en/api-docs/notification-groups) and [escalation policies](https://nodea.io/en/api-docs/escalation-policies), which route alerts through these channels.

## Authentication and requirements

All endpoints live under the /api/v1 prefix (full host example: https://app.nodea.io/api/v1). Authentication uses a Passport personal access token in the Authorization: Bearer \<token\> header. Rate limit: throttle:api-v1 = 240 requests per minute.

- **Scopes:** read = alerting:read, write = alerting:write, delete = alerting:delete. alerting:write covers create, update and non-destructive actions (test, every-fail); alerting:delete covers destroy only.
- **Feature flag:** integration routes require the Pennant flag integrations to be enabled.
- **Ownership scope:** an integration is always scoped to the token owner. A foreign or unknown id returns 404 (it never confirms the existence of another user's resource).
- **Validation errors** → HTTP 422 with body { "message": ..., "errors": { field: \[..\] } }.

**Integration types:** slack, mail, telegram, discord, ms\_teams, google\_chat, webhook, pagerduty, opsgenie, sms. Each type has one canonical config key: slack → slack\_webhook\_url, mail → email\_address, discord → discord\_webhook\_url, ms\_teams → webhook\_url, google\_chat → google\_webhook\_url, webhook → webhook\_url, pagerduty → integration\_key, opsgenie → api\_key (plus optional region: us|eu), sms → phone\_number (E.164), telegram → chat\_id + integration\_code.

**Telegram is web-UI only.** Telegram setup is an interactive bot-pairing flow (redirect to telegram.me with a single-use integration\_code) that a headless client cannot complete. Creating and editing Telegram over the API/MCP is therefore rejected — only read, test and delete are supported.

## Secret masking — the key rule

**Credentials are NEVER returned in plaintext.** Every response (API and MCP) passes through IntegrationResource, which masks all secret keys: slack\_webhook\_url, discord\_webhook\_url, webhook\_url, google\_webhook\_url, integration\_key, api\_key, integration\_code. Mask format: \*\*\*\* plus the last 4 characters; secrets of 8 characters or fewer collapse to a bare \*\*\*\* (the mask never reveals half of a short credential). Routing keys stay readable: email\_address, phone\_number, chat\_id (Telegram — useless without the bot token), region (OpsGenie data-center picker).

**Sending a masked value back on update = no change.** On PATCH/PUT, secret values that arrive empty or masked (starting with \*\*\*\*) are stripped BEFORE validation. As a result, fetching an integration and sending it back is a no-op for the secret (the original stored credential is kept) and the mask never hits the url rule of a webhook field. To rotate a secret, pass the full new value.

## API v1

MethodPathScopeDescriptionGET/integrationsalerting:readList integrations (filters: type, search; sort, pagination).GET/integrations/{integration}alerting:readA single integration.POST/integrationsalerting:writeCreate an integration.PATCH / PUT/integrations/{integration}alerting:writePartial update (type immutable, Telegram rejected).DELETE/integrations/{integration}alerting:deletePermanently delete (204 No Content).POST/integrations/{integration}/testalerting:writeSend a test notification.POST/integrations/{integration}/every-failalerting:writeToggle the every\_fail flag.

### Response shape

Every integration resource has: id, name, type, every\_fail (bool), is\_ready (bool — whether the credentials are ready to send), config (object with masked secrets), created\_at, updated\_at (ISO 8601) and health (delivery-state object — see below).

### Health (health)

Every resource carries a health object describing whether notifications through the integration are being delivered. It updates automatically on every delivery attempt and cannot be set through the API.

- status — healthy (working), failing (recent deliveries failed, alerts are still sent) or suspended (failures persisted, so alert delivery through this integration is **paused**).
- consecutive\_failures (int) — failed deliveries since the last success.
- first\_failure\_at, last\_failure\_at, last\_success\_at, suspended\_at — ISO 8601 or null. last\_success\_at is refreshed at most every 15 minutes.
- next\_probe\_at — when a suspended integration is checked automatically next (only while status = suspended, otherwise null). null on a suspended integration means checking has stopped.
- probe\_attempts (int) — failed checks since the suspension.
- last\_error — short description of the last error (max 500 characters, URL cut to its host, secrets removed) or null for a working integration.
- last\_error\_kind — permanent (e.g. HTTP 404, revoked token) | transient (timeout, 5xx) | null.
- auto\_probe (bool) — whether a suspended integration is re-checked automatically. false for pagerduty, opsgenie and sms (a test message would open a real incident or cost money) — those types resume when a fixed configuration is saved.
- manual\_test\_supported (bool) — whether POST /integrations/{integration}/test works for the type.

**Resuming delivery:** a successful test (POST …/test, MCP tool integration-test), saving the configuration (PATCH/PUT, integration-update) or a successful automatic check brings status back to healthy.

"health": { "status": "suspended", "consecutive\_failures": 6, "first\_failure\_at": "2026-10-11T08:12:04+00:00", "last\_failure\_at": "2026-10-11T09:40:51+00:00", "last\_success\_at": "2026-10-10T22:03:17+00:00", "suspended\_at": "2026-10-11T09:40:51+00:00", "next\_probe\_at": "2026-10-11T10:10:51+00:00", "probe\_attempts": 0, "last\_error": "DeliveryFailedException: Webhook responded with HTTP 404.", "last\_error\_kind": "permanent", "auto\_probe": true, "manual\_test\_supported": true }

### GET /integrations — list

Query parameters: search (string, max 255 — matched against name and type), type (one of the integration types), sort (name|type|created\_at), direction (asc|desc), per\_page (1–100, default 20), page. The response is paginated (data envelope + meta/links).

curl -s https://app.nodea.io/api/v1/integrations?type=slack&per\_page=20 \\ -H "Authorization: Bearer $TOKEN" \\ -H "Accept: application/json"

### POST /integrations — create

Body: name (required, max 100), type (required; telegram rejected with 422), every\_fail (optional, bool), and the canonical config key for the chosen type (see the type table above). Config rules are per-type: webhook URLs must be valid, safe public URLs; email\_address is validated with email:rfc,dns; phone\_number must be E.164 (+ and 7–15 digits); integration\_key/api\_key require at least 10 characters.

Create error codes: 402 with { "code": "plan\_limit\_exceeded" } when the plan's integration limit is reached; 422 with { "code": "missing\_config" } when the required config key for the chosen type is missing. Success returns 201 Created with the masked resource.

curl -s -X POST https://app.nodea.io/api/v1/integrations \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -H "Accept: application/json" \\ -d '{ "name": "Ops Slack", "type": "slack", "every\_fail": false, "slack\_webhook\_url": "https://hooks.slack.com/services/T000/B000/XXXX" }'

### PATCH /integrations/{integration} — update

The update is partial: omitted fields stay unchanged. type is **immutable** (the prohibited rule — changing the provider would orphan the stored config; create a new integration instead). telegram integrations are rejected with 422 and { "code": "telegram\_not\_supported" }. Masked or empty secrets are ignored (see the [Secret masking](#masking) section).

curl -s -X PATCH https://app.nodea.io/api/v1/integrations/42 \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -H "Accept: application/json" \\ -d '{ "name": "Ops Slack (prod)", "every\_fail": true }'

### POST /integrations/{integration}/test — test notification

Sends a test notification using the same service the web UI uses. Supported types: slack, mail, telegram, ms\_teams, discord, google\_chat. Other types (webhook, pagerduty, opsgenie, sms) return 422 with { "code": "test\_not\_supported" }.

**Seeder-data protection:** if the integration holds placeholder (seeder) credentials, the test returns 422 with { "code": "placeholder\_credentials" } WITHOUT sending anything to an external API. When the provider rejects the send (bad webhook, revoked token), it returns 502 with { "code": "test\_failed", "reason": ... }. Success: { "message": "Test notification sent." }. A successful test lifts a suspension (health.status returns to healthy) without sending an email.

curl -s -X POST https://app.nodea.io/api/v1/integrations/42/test \\ -H "Authorization: Bearer $TOKEN" \\ -H "Accept: application/json"

### POST /integrations/{integration}/every-fail — every\_fail flag

Body: enabled (required, bool). true = notify on EVERY failed run; false = only on state changes (fail/recover). Returns the updated resource.

curl -s -X POST https://app.nodea.io/api/v1/integrations/42/every-fail \\ -H "Authorization: Bearer $TOKEN" \\ -H "Content-Type: application/json" \\ -H "Accept: application/json" \\ -d '{ "enabled": true }'

### Error codes

- 401 — missing or invalid token.
- 403 — token without the required scope.
- 404 — integration does not exist or belongs to another user.
- 422 — validation error (or missing\_config, telegram\_not\_supported, placeholder\_credentials, test\_not\_supported).
- 402 — plan limit exceeded on create (plan\_limit\_exceeded).
- 502 — provider rejected the test (test\_failed).

## MCP

The MCP server exposes 7 tools for managing integrations. All require the integrations feature flag and the appropriate scope. Secrets are masked exactly as in the API. Tools marked **destructive** require an explicit confirm: true.

NameScopeModeDescriptionintegrations-listalerting:readread-onlyList the user's integrations (filter type, search, pagination).integration-getalerting:readread-onlyA single integration by id.integration-createalerting:writewriteCreate an integration (Telegram not allowed).integration-updatealerting:writewritePartial update (type immutable, Telegram not allowed).integration-testalerting:writewriteSend a test notification.integration-set-every-failalerting:writewriteToggle the every\_fail flag.integration-deletealerting:deletedestructivePermanently delete — requires confirm: true.

### integrations-list

Parameters: type (optional type filter), search (optional name substring), page (min 1), per\_page (1–50, default 15). Returns data (masked integrations, each with the same health object as the API) + meta (pagination). integration-get returns the same shape for one integration.

{ "method": "tools/call", "params": { "name": "integrations-list", "arguments": { "type": "slack", "per\_page": 15 } } }

### integration-create

Parameters: name (required), type (required; Telegram excluded from the enum), config (required object holding the credential key for the type), every\_fail (default false). Same plan limit (create-integration) as the API.

{ "method": "tools/call", "params": { "name": "integration-create", "arguments": { "name": "Ops Slack", "type": "slack", "config": { "slack\_webhook\_url": "https://hooks.slack.com/services/T000/B000/XXXX" } } } }

### integration-update

Parameters: id (required), and optionally name, config, every\_fail. Passing a masked config value (\*\*\*\*..., as the read tools return it) keeps the stored secret; pass a full new value to rotate it.

{ "method": "tools/call", "params": { "name": "integration-update", "arguments": { "id": 42, "config": { "slack\_webhook\_url": "\*\*\*\*XXXX" }, "every\_fail": true } } }

### integration-test

Parameters: id (required). Placeholder (seeder) credentials are refused without touching any external API. Types without a test sender (webhook, pagerduty, opsgenie, sms) return an explicit error (the web UI offers no test button for them). A successful test lifts a suspension.

{ "method": "tools/call", "params": { "name": "integration-test", "arguments": { "id": 42 } } }

### integration-delete (destructive)

Parameters: id (required) and confirm (required, must be true). Without confirm: true the deletion is refused so an agent cannot delete an integration on a loosely-parsed instruction. Notification groups and escalation steps referencing it lose this channel. The operation is irreversible.

{ "method": "tools/call", "params": { "name": "integration-delete", "arguments": { "id": 42, "confirm": true } } }

## Related

- [Notification Groups — API v1 and MCP Tools | Nodea](https://nodea.io/en/api-docs/notification-groups)
- [Escalation Policies — API v1 & MCP | Nodea](https://nodea.io/en/api-docs/escalation-policies)

---

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