Nodea — logo

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: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: slackslack_webhook_url, mailemail_address, discorddiscord_webhook_url, ms_teamswebhook_url, google_chatgoogle_webhook_url, webhookwebhook_url, pagerdutyintegration_key, opsgenieapi_key (plus optional region: us|eu), smsphone_number (E.164), telegramchat_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

MethodPathScopeDescription
GET/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).

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 (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.

NameScopeModeDescription
integrations-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) + 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 }
  }
}