Nodea — logo

Resource shares — API v1 and MCP | Nodea

Share resources let you grant another registered account read access to a website, heartbeat or server you own. It is the API v1 REST and MCP counterpart of the panel's /app/share/* page. Sharing is not gated by a module feature flag (no features: middleware, exactly like web sharing) — you only need the relevant scope from the alerting group.

Every endpoint lives under the base path /api/v1 (example host: https://app.nodea.io/api/v1). Authentication uses a Passport personal access token in the Authorization: Bearer <token> header. The rate limit throttle:api-v1 = 240 requests/min applies. Required scopes: read alerting:read, write alerting:write, delete alerting:delete. Access is checked per row in the controller — a foreign or unknown share id yields 404.

API v1

MethodPathScopeDescription
GET/sharesalerting:readList shares in the three-box model.
POST/sharesalerting:writeShare a resource you own with another account.
POST/shares/{share}/acceptalerting:writeAccept a pending invitation addressed to you.
POST/shares/{share}/declinealerting:writeDecline a pending invitation addressed to you.
DELETE/shares/{share}alerting:deleteDelete a share: revoke (as owner) or leave (as recipient).

GET /shares — the three-box model

The listing is organised into three boxes, mirroring the panel's /sharing page. The box query parameter accepts one of:

  • incoming_pending — invitations addressed to me, awaiting my response (foreign FOREIGN rows with a null accepted_at).
  • incoming_accepted — shares I accepted from others.
  • outbound — invitations I sent (foreign FOREIGN rows on resources I own), together with the recipient.

When you pass box, a single set is returned simple-paginated (per_page, default 20, max 100; page). When you omit box, the endpoint returns all three boxes in one JSON object (keys incoming_pending, incoming_accepted, outbound), each capped at per_page rows — use box to page through a specific set.

curl -s https://app.nodea.io/api/v1/shares?box=incoming_pending \
  -H "Authorization: Bearer $TOKEN"

Each response row (ShareResource) contains:

  • id — the share identifier.
  • resource_typewebsite, heartbeat or server.
  • resource — a compact summary of the shared item (id, name, plus url for websites and ip for servers). It is null if the item was deleted after the share was created.
  • statuspending or accepted.
  • accepted_at, created_at — ISO 8601 timestamps.
  • recipient — the account the item is shared with (id, name, email).
  • owner — the owner of the underlying resource (or null).

POST /shares — share a resource

The body requires three fields: resource_type (enum website | heartbeat | server), resource_id (the resource id — websites and heartbeats use integer ids, servers use UUIDs) and the email of the invitee (must belong to a registered user).

The resource must be owned by the caller. Unknown and foreign ids collapse into the same 422 error so the endpoint cannot be used to enumerate other tenants' ids. Attempting to share a resource with yourself also returns 422.

For websites and heartbeats a pending invitation is created (a pending share) and a notification sent — unless the recipient auto-accepts shares from the sender (then accepted immediately). Server shares have no handshake and become active at once (accepted_at is stamped immediately). Success returns 201 Created with the share representation. Re-sharing an already-shared resource returns 409 Conflict with code already_shared. Signed-URL invitation links stay web-only.

curl -s -X POST https://app.nodea.io/api/v1/shares \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"resource_type":"website","resource_id":"42","email":"collab@example.com"}'

POST /shares/{share}/accept and /decline — the recipient handshake

An invitation can be accepted or declined only by the recipient of a pending FOREIGN row addressed to them. Anyone else — including the resource owner — gets a 404 (it is simply not their invitation). accept stamps accepted_at = now() and returns the updated resource; decline deletes the row and returns 204 No Content (the owner may send a new invitation later).

curl -s -X POST https://app.nodea.io/api/v1/shares/123/accept \
  -H "Authorization: Bearer $TOKEN"

DELETE /shares/{share} — revoke vs leave

This endpoint deletes only FOREIGN rows (an OWNER row is the owner's own visibility bookkeeping — deleting it returns 404). The controller decides the role and meaning of the operation itself:

  • revoke — if the caller owns the underlying resource, it removes the recipient's access.
  • leave — if the caller is the recipient of an accepted share, they leave it themselves.

A pending invitation is not deleted here — use the decline endpoint to reject it. Success returns 204 No Content. No rights to the row (neither owner nor accepted recipient) yields 404.

curl -s -X DELETE https://app.nodea.io/api/v1/shares/123 \
  -H "Authorization: Bearer $TOKEN"

Error codes

CodeMeaning
401Missing or invalid access token.
403Token lacks the required scope (alerting:read / write / delete).
404Unknown or foreign share id — access is checked per row.
422Validation error: unknown/foreign resource, sharing with yourself, bad box.

MCP

The Nodea MCP server exposes five sharing tools. Each enforces the same scope as its REST counterpart. A read-only mode never changes state; a destructive mode requires an explicit confirm: true so an agent cannot cut off someone's access on a loosely-parsed instruction.

NameScopeModeDescription
shares-listalerting:readread-onlyList the user's shares with a box marker on each row.
share-createalerting:writewrite (idempotent)Share a resource you own with another account by email.
share-acceptalerting:writewriteAccept a pending invitation addressed to you.
share-declinealerting:writewriteDecline a pending invitation (the owner may send a new one).
share-revokealerting:deletedestructive (confirm: true)Revoke (owner) or leave (recipient) an active share.

The share-create tool is idempotent — re-sharing an already-shared resource is a no-op (status: already_shared). The share-accept and share-decline tools operate only on a pending invitation addressed to the caller; they need no confirmation because nothing irreversible is lost (a declined invitation can be re-sent by the owner). Only share-revoke is marked destructive and requires confirm: true.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "shares-list",
    "arguments": { "box": "incoming_pending", "per_page": 15 }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "share-create",
    "arguments": {
      "resource_type": "website",
      "resource_id": "42",
      "recipient_email": "collab@example.com"
    }
  }
}
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "share-revoke",
    "arguments": { "id": 123, "confirm": true }
  }
}