# List Contributor Network comments

```http
GET /api/public/v1/contributor-network/items/{itemId}/comments
```

**Scope**: `contributor-network.read`.

Lists visible comments and redacted thread-position tombstones for one active public item. Reading comments requires neither competition enrollment nor account setup.

## Path parameter

| Parameter | Requirement |
| :--- | :--- |
| `itemId` | **Required string.** Opaque Contributor Network item ID. |

## Query parameters

| Parameter | Type, requirement, default, and meaning |
| :--- | :--- |
| `sort` | **Optional string.** `latest` (default) or `earliest`. |
| `cursor` | **Optional string.** Opaque cursor returned by the preceding page. |
| `limit` | **Optional integer.** Page size from `1` through `100`; defaults to `25`. |

Send each parameter at most once. Unknown query parameters are rejected.

## Ordering and pagination

`latest` orders by `createdAt` descending and then comment ID descending. `earliest` uses both fields in ascending order. Equal timestamps are ordered consistently across page boundaries.

The cursor is bound to the API key owner, endpoint, `itemId`, and sort direction. Pass it through unchanged; changing any bound value requires a new traversal without a cursor. A page's `limit` counts visible comments and tombstones.

Pagination is not a snapshot. A newly created comment may sort before or after the current cursor, so it can be absent from the remaining pages or appear on a later page according to the selected order. Start a fresh traversal to refresh the thread, and de-duplicate merged pages by comment ID. A comment deleted during traversal remains at the same position as a tombstone.

## Response

Returns `200 OK` with:

| Field | Type | Description |
| :--- | :--- | :--- |
| `items` | `Comment[]` | Comment records. |
| `nextCursor` | string \| null | Cursor for the next page, or `null` after the final page. |

### Comment fields

| Field | Type | Meaning |
| :--- | :--- | :--- |
| `id` | string | Stable opaque comment ID. |
| `author` | `CommentAuthor` or `null` | Public author snapshot for a visible comment; `null` for a tombstone. |
| `body` | string or `null` | Immutable trimmed comment body, or `null` for a tombstone. |
| `createdAt` | string | Original ISO 8601 UTC creation timestamp. |
| `isDeleted` | boolean | Public tombstone flag; `true` whenever the comment is no longer visible. |
| `viewerCanDelete` | boolean | Whether this API key can currently delete the visible comment. |

`CommentAuthor` contains `sairId`, `displayName`, and nullable `avatarUrl`. The display name and avatar are the public snapshot captured when the comment was created, not a live profile projection.

For a visible comment, `author` and `body` are non-null and `isDeleted` is `false`. `viewerCanDelete` is `true` only when this key has `contributor-network.write`, its owner has completed account setup, and the owner is either the comment author or the item author.

A deleted or moderated comment remains as a thread-position tombstone with `author: null`, `body: null`, its original `createdAt`, `isDeleted: true`, and `viewerCanDelete: false`. The response does not reveal the original body, author, removal reason, deletion time, or moderator identity.

```json
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "cmt_01K5A7V40ZQ3YV7DJM8M5XK1T2",
        "author": {
          "sairId": "U-9f7e1c00",
          "displayName": "Ada",
          "avatarUrl": null
        },
        "body": "Reproduced from the pinned revision.",
        "createdAt": "2026-05-18T12:34:56Z",
        "isDeleted": false,
        "viewerCanDelete": false
      },
      {
        "id": "cmt_01K5A7P9R2V1S6H6F9W0N3D4C5",
        "author": null,
        "body": null,
        "createdAt": "2026-05-18T12:20:00Z",
        "isDeleted": true,
        "viewerCanDelete": false
      }
    ],
    "nextCursor": null
  }
}
```

The `commentCount` exposed by item summaries counts visible comments only. It can therefore be smaller than the number of records traversed here when tombstones exist.

Successful responses use `Content-Type: application/json` and `Cache-Control: private, no-store` because deletion capability is caller-specific and thread state can change.

## Errors

| HTTP | Code | When |
| :---: | :--- | :--- |
| `400` | `MALFORMED_BODY` | The cursor is invalid or bound to another traversal, or `limit` is outside its range. |
| `404` | `NOT_FOUND` | The item or its owning Competition does not exist, is not active and publicly visible, or does not expose Contributor Network. |
| `422` | `RESOURCE_FIELD_INVALID` | `sort` is blank or unsupported, or a query parameter is unknown or repeated. |

See [Errors](../../errors.md) for shared errors.
