Skip to main content
Members can save content nodes to a personal list (“My List”) within a hub. Saves persist across sessions, survive subscription lapses, and are always consistent with what the member can actually open right now.
POST   /api/v1/hub/{hub_id}/saved-items                          save a content node
DELETE /api/v1/hub/{hub_id}/saved-items/{content_node_id}        unsave
GET    /api/v1/hub/{hub_id}/me/saved-items                       retrieve the list (newest-first)
All three routes also resolve under the /api prefix (no v1).

Saving an item

Send the content node’s ID in the request body:
curl -X POST https://api.member.dev/api/v1/hub/{hub_id}/saved-items \
  -H "Authorization: Bearer <contact_jwt>" \
  -H "Content-Type: application/vnd.api+json" \
  -d '{
    "data": {
      "type": "saved_items",
      "attributes": {
        "content_node_id": "01926b3e-cde4-7000-8000-000000000001"
      }
    }
  }'
Returns 201 with the saved_items resource:
{
  "data": {
    "type": "saved_items",
    "id": "01926b3e-ffff-7000-8000-000000000042",
    "attributes": {
      "hub_id": "01926b3e-aaaa-7000-8000-000000000010",
      "content_node_id": "01926b3e-cde4-7000-8000-000000000001",
      "created_at": "2026-06-10T14:00:00Z"
    }
  }
}
Idempotent. Saving the same node a second time returns 201 with the original row — the created_at date and list position are not changed. No duplicate event is emitted. This makes double-taps from the UI safe to retry. Access-gated. You can only save a node you can currently open. If the node is missing or the member does not have access, the API returns 404 — the same response for both cases, so no information about whether the node exists leaks.

Unsaving an item

curl -X DELETE https://api.member.dev/api/v1/hub/{hub_id}/saved-items/{content_node_id} \
  -H "Authorization: Bearer <contact_jwt>"
Returns 204 always. Unsaving a node that was never saved is not an error. The path parameter is the content_node_id, not the saved_items row ID.

Retrieving the list

curl "https://api.member.dev/api/v1/hub/{hub_id}/me/saved-items?page[size]=24" \
  -H "Authorization: Bearer <contact_jwt>"
Returns saved items newest-first, with full content_nodes resources in the top-level included array so the frontend can render cards without additional fetches:
{
  "data": [
    {
      "type": "saved_items",
      "id": "01926b3e-ffff-7000-8000-000000000042",
      "attributes": {
        "hub_id": "01926b3e-aaaa-7000-8000-000000000010",
        "content_node_id": "01926b3e-cde4-7000-8000-000000000001",
        "created_at": "2026-06-10T14:00:00Z"
      },
      "relationships": {
        "content_node": {
          "data": { "type": "content_nodes", "id": "01926b3e-cde4-7000-8000-000000000001" }
        }
      }
    }
  ],
  "included": [
    {
      "type": "content_nodes",
      "id": "01926b3e-cde4-7000-8000-000000000001",
      "attributes": {
        "title": "Getting started",
        "is_saved": true,
        ...
      }
    }
  ],
  "links": {
    "next": "/api/v1/hub/{hub_id}/me/saved-items?page[after]=01926b3e-ffff-7000-8000-000000000042&page[size]=24"
  },
  "meta": { "has_more": true }
}
Pagination uses page[size] (1–100, default 20) and page[after] (cursor from the previous response’s links.next). See pagination for details.

Access behavior

Items whose access has been revoked are hidden from the list — they do not appear but are not deleted. If the member’s access is restored, the item reappears with its original save date. This means saving a node during a subscription and then losing access does not silently discard the bookmark; it comes back exactly where it was when the subscription renews. Consequently, the list length may be shorter than page[size] even when has_more is true — the cursor still advances correctly past hidden items.

is_saved on content reads

The portal content routes include an is_saved boolean on every content_nodes resource so the UI can show the bookmark state inline without a separate request:
GET /api/v1/hub/{hub_id}/content
GET /api/v1/hub/{hub_id}/content/{node_id}
GET /api/v1/hub/{hub_id}/content/{node_id}/children
is_saved is false for unauthenticated requests.

Auth and rate limits

All three endpoints require a contact JWT (hub member). Anonymous requests return 401. A non-member returns 403.
RouteRate limit
POST and DELETE120 requests / hour
GET300 requests / hour

v1 scope

Saved items currently target content nodes only. The underlying data model supports other target types (discussions, members) — those will be exposed in a future release without a contract change.