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
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /shares | alerting:read | List shares in the three-box model. |
POST | /shares | alerting:write | Share a resource you own with another account. |
POST | /shares/{share}/accept | alerting:write | Accept a pending invitation addressed to you. |
POST | /shares/{share}/decline | alerting:write | Decline a pending invitation addressed to you. |
DELETE | /shares/{share} | alerting:delete | Delete 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 (foreignFOREIGNrows with a nullaccepted_at).incoming_accepted— shares I accepted from others.outbound— invitations I sent (foreignFOREIGNrows 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_type—website,heartbeatorserver.resource— a compact summary of the shared item (id,name, plusurlfor websites andipfor servers). It isnullif the item was deleted after the share was created.status—pendingoraccepted.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 (ornull).
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
| Code | Meaning |
|---|---|
401 | Missing or invalid access token. |
403 | Token lacks the required scope (alerting:read / write / delete). |
404 | Unknown or foreign share id — access is checked per row. |
422 | Validation 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.
| Name | Scope | Mode | Description |
|---|---|---|---|
shares-list | alerting:read | read-only | List the user's shares with a box marker on each row. |
share-create | alerting:write | write (idempotent) | Share a resource you own with another account by email. |
share-accept | alerting:write | write | Accept a pending invitation addressed to you. |
share-decline | alerting:write | write | Decline a pending invitation (the owner may send a new one). |
share-revoke | alerting:delete | destructive (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 }
}
}