Notification Integrations — API v1 and MCP | Nodea
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 and 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:writecovers create, update and non-destructive actions (test, every-fail);alerting:deletecovers destroy only. - Feature flag: integration routes require the Pennant flag
integrationsto 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
422with 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
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /integrations | alerting:read | List integrations (filters: type, search; sort, pagination). |
| GET | /integrations/{integration} | alerting:read | A single integration. |
| POST | /integrations | alerting:write | Create an integration. |
| PATCH / PUT | /integrations/{integration} | alerting:write | Partial update (type immutable, Telegram rejected). |
| DELETE | /integrations/{integration} | alerting:delete | Permanently delete (204 No Content). |
| POST | /integrations/{integration}/test | alerting:write | Send a test notification. |
| POST | /integrations/{integration}/every-fail | alerting:write | Toggle 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).
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 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." }.
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 (ormissing_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.
| Name | Scope | Mode | Description |
|---|---|---|---|
integrations-list | alerting:read | read-only | List the user's integrations (filter type, search, pagination). |
integration-get | alerting:read | read-only | A single integration by id. |
integration-create | alerting:write | write | Create an integration (Telegram not allowed). |
integration-update | alerting:write | write | Partial update (type immutable, Telegram not allowed). |
integration-test | alerting:write | write | Send a test notification. |
integration-set-every-fail | alerting:write | write | Toggle the every_fail flag. |
integration-delete | alerting:delete | destructive | Permanently 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) + meta (pagination).
{
"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 (unlike the web UI, which silently shows success).
{
"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 }
}
}