Nodea — logo

Heartbeats — API v1 and MCP | Nodea Documentation

The Heartbeats module handles cron / dead-man-switch monitors: a service or cron job must periodically call the public ping URL (/hb/{token}), and a missed deadline (interval + grace) raises an alert. This document describes the REST API v1 endpoints and the module's MCP tools. The public ping endpoint is a separate, unauthenticated web route and is not part of this API.

Authentication, scopes and flag

Passport Bearer token (Authorization: Bearer <token>). Scopes: monitoring:read (index, show, runs), monitoring:write (create, update, pause, resume), monitoring:delete (delete). The whole module is gated by the features:heartbeats flag.

Ownership, visibility and token masking

Access is enforced by HeartbeatPolicy: view = owner or accepted FOREIGN share, manage = owner only. An existing but invisible id yields 403, an unknown id 404. Key security rule: the token and the derived ping_url are secrets (the customer's cron URL is derived from them), so they are serialized only for the owner (passing the manage policy). An accepted share sees the heartbeat but never its token. The token is generated once on create and is immutable — update never regenerates it.

API v1 — endpoints

MethodPathScopeDescription
GET/api/v1/heartbeatsmonitoring:readList heartbeats (owned + accepted shares), paginated.
GET/api/v1/heartbeats/{heartbeat}monitoring:readDetail; meta.can_manage. Token for owner only.
GET/api/v1/heartbeats/{heartbeat}/runsmonitoring:readPing/miss history, newest first.
POST/api/v1/heartbeatsmonitoring:writeCreate a heartbeat (generates token). Returns 201. Plan limit → 402 (code plan_limit_exceeded).
PUT/PATCH/api/v1/heartbeats/{heartbeat}monitoring:writeUpdate name/interval/grace/note. Token immutable.
POST/api/v1/heartbeats/{heartbeat}/pausemonitoring:writeIdempotent pause (sets paused_at).
POST/api/v1/heartbeats/{heartbeat}/resumemonitoring:writeIdempotent resume (clears paused_at).
DELETE/api/v1/heartbeats/{heartbeat}monitoring:deletePermanently delete (history cascade). Returns 204.

GET /heartbeats — query parameters

  • search (string, max 255) — matched against name.
  • status (enum: active|paused).
  • sort (enum: name|interval_seconds|last_ping_at|created_at, default created_at).
  • direction (asc|desc).
  • per_page (int 1–100, default 20), page.

POST/PATCH — body parameters

  • name (string, required, max 255).
  • intervalSeconds (int, required, 60–259200) — expected ping interval.
  • graceSeconds (int, required, 0–86400) — grace period added on top of the interval.
  • note (string, optional, max 5000).
  • notificationGroupsId[] (POST only) — must belong to the token user (otherwise 422). Update does not accept group sync.

Response shape (HeartbeatResource)

Fields: id, name, status (paused→waiting→late→ok), interval_seconds, grace_seconds, note, token (owner only), ping_url (owner only), ownership (owner|shared), last_ping_at, deadline_at (only after the first ping), paused_at, created_at, updated_at. History entry (HeartbeatRunResource): id, heartbeat_id, state, state_name (Success = ping / Failure = missed deadline), reason, reason_code, created_at.

Error codes

  • 401 — missing/invalid token; 403 — scope/policy; 404 — unknown id; 422 — validation or a foreign notification group; 402 — plan limit exceeded on create (plan_limit_exceeded).

Examples — API

Create a heartbeat (every 5 min, 60s grace)

curl -s -X POST https://app.nodea.io/api/v1/heartbeats \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Nightly backup",
    "intervalSeconds": 300,
    "graceSeconds": 60,
    "note": "cron 0 3 * * *"
  }'

The response includes token and ping_url — the one-time moment to wire up the cron. The script then calls ping_url after each successful run.

List active heartbeats

curl -s "https://app.nodea.io/api/v1/heartbeats?status=active&sort=last_ping_at&direction=desc" \
  -H "Authorization: Bearer $NODEA_TOKEN"

MCP — tools

Same auth/scope model as API v1 (ensureScope) and the features:heartbeats flag. The token and ping_url appear in the response only when the caller holds the manage ability (owner).

ToolScopeTypeDescription
heartbeats-listmonitoring:readread-onlyList; search, status (active/paused), page, per_page (max 50). No token.
heartbeat-getmonitoring:readread-onlyDetail; token + ping_url for owner only. Param: id.
heartbeat-createmonitoring:writewritename, interval_seconds (60–259200), grace_seconds (0–86400), note. Plan limit → error. Returns token+ping_url.
heartbeat-updatemonitoring:writeidempotentPartial update of name/interval/grace/note. Owner only. Token immutable.
heartbeat-set-pausedmonitoring:writeidempotentPause/resume via paused: true|false.
heartbeat-deletemonitoring:deletedestructivePermanent delete; requires confirm: true.
heartbeat-runsmonitoring:readread-onlyPing/miss history; page, per_page (max 50).

Example — MCP (JSON-RPC)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "heartbeat-create",
    "arguments": {
      "name": "Nightly backup",
      "interval_seconds": 300,
      "grace_seconds": 60,
      "note": "cron 0 3 * * *"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "heartbeat-set-paused",
    "arguments": { "id": "3af0...uuid", "paused": false }
  }
}