> ## Documentation Index
> Fetch the complete documentation index at: https://docs.member.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Saved items (My List)

> Let members bookmark content and retrieve their personal list with a single API call.

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.

```text theme={null}
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:

```bash theme={null}
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:

```json theme={null}
{
  "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

```bash theme={null}
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

```bash theme={null}
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:

```json theme={null}
{
  "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](/reference/pagination-filtering) 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:

```text theme={null}
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`.

| Route               | Rate limit          |
| ------------------- | ------------------- |
| `POST` and `DELETE` | 120 requests / hour |
| `GET`               | 300 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.
