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

# Get Session Messages

> List, filter, and poll messages on a session or a single thread.

Two endpoints share the same paginated response shape and query parameters. Use the first to list every message in a session; use the second to scope to a single thread (for example, to poll the latest follow-up's reply).

```http theme={null}
GET /rest/v1/sessions/{session_id}/messages
GET /rest/v1/sessions/{session_id}/threads/{thread_id}/messages
```

Results are sorted by `created_at` (newest first by default). Soft-deleted messages are excluded.

## Request

<CodeGroup>
  ```javascript JavaScript theme={null}
  // All messages on the session.
  const url = `https://api.blocks.team/rest/v1/sessions/${sessionId}/messages?type=final_message&role=assistant`;

  // Or scope to a specific thread:
  // const url = `https://api.blocks.team/rest/v1/sessions/${sessionId}/threads/${threadId}/messages?type=final_message&role=assistant`;

  const page = await fetch(url, {
    headers: { Authorization: `ApiKey ${process.env.BLOCKS_API_KEY}` },
  }).then((r) => r.json());
  ```

  ```python Python theme={null}
  import os, requests

  # All messages on the session.
  url = f"https://api.blocks.team/rest/v1/sessions/{session_id}/messages"

  # Or scope to a specific thread:
  # url = f"https://api.blocks.team/rest/v1/sessions/{session_id}/threads/{thread_id}/messages"

  page = requests.get(
      url,
      headers={"Authorization": f"ApiKey {os.environ['BLOCKS_API_KEY']}"},
      params={"type": "final_message", "role": "assistant"},
  ).json()
  ```

  ```bash cURL theme={null}
  # All messages on the session.
  curl "https://api.blocks.team/rest/v1/sessions/$SESSION_ID/messages?type=final_message&role=assistant" \
    -H "Authorization: ApiKey $BLOCKS_API_KEY"

  # Or scope to a specific thread:
  curl "https://api.blocks.team/rest/v1/sessions/$SESSION_ID/threads/$THREAD_ID/messages?type=final_message&role=assistant" \
    -H "Authorization: ApiKey $BLOCKS_API_KEY"
  ```

  ```java Java theme={null}
  import java.net.URI;
  import java.net.http.*;

  String url = "https://api.blocks.team/rest/v1/sessions/" + sessionId
      + "/messages?type=final_message&role=assistant";

  // Or scope to a specific thread:
  // String url = "https://api.blocks.team/rest/v1/sessions/" + sessionId
  //     + "/threads/" + threadId + "/messages?type=final_message&role=assistant";

  HttpResponse<String> res = HttpClient.newHttpClient().send(
      HttpRequest.newBuilder(URI.create(url))
          .header("Authorization", "ApiKey " + System.getenv("BLOCKS_API_KEY"))
          .build(),
      HttpResponse.BodyHandlers.ofString());
  ```

  ```ruby Ruby theme={null}
  require "net/http"
  require "json"

  # All messages on the session.
  url = "https://api.blocks.team/rest/v1/sessions/#{session_id}/messages?type=final_message&role=assistant"

  # Or scope to a specific thread:
  # url = "https://api.blocks.team/rest/v1/sessions/#{session_id}/threads/#{thread_id}/messages?type=final_message&role=assistant"

  page = JSON.parse(Net::HTTP.get(URI(url), {
    "Authorization" => "ApiKey #{ENV['BLOCKS_API_KEY']}",
  }))
  ```

  ```go Go theme={null}
  url := "https://api.blocks.team/rest/v1/sessions/" + sessionID +
      "/messages?type=final_message&role=assistant"

  // Or scope to a specific thread:
  // url := "https://api.blocks.team/rest/v1/sessions/" + sessionID +
  //     "/threads/" + threadID + "/messages?type=final_message&role=assistant"

  req, _ := http.NewRequest("GET", url, nil)
  req.Header.Set("Authorization", "ApiKey "+os.Getenv("BLOCKS_API_KEY"))
  res, _ := http.DefaultClient.Do(req)
  ```
</CodeGroup>

### Path parameters

<ParamField path="session_id" type="string (uuid)" required>
  The session ID. Required for both URL forms.
</ParamField>

<ParamField path="thread_id" type="string (uuid)" required>
  The thread ID. Required only on the threaded URL form. The thread must belong to `session_id`.
</ParamField>

### Query parameters

<ParamField query="type" type="string[]" default="[&#x22;message&#x22;, &#x22;final_message&#x22;]">
  Repeat to combine. Allowed values: `message`, `final_message`, `tool_call`. Pass `type=final_message` to fetch only completed assistant replies.
</ParamField>

<ParamField query="role" type="string" default="assistant">
  Filter by sender. One of `user`, `assistant`.
</ParamField>

<ParamField query="thread_id" type="string (uuid)">
  Filter to a single thread without using the threaded URL form. Ignored on the threaded URL.
</ParamField>

<ParamField query="gts" type="number">
  Cursor for incremental polling, in epoch seconds. Returns only messages whose `ts` is strictly greater than this value. Use `_links.new_messages.href` from the previous page to get a pre-built URL with this cursor already set.
</ParamField>

<ParamField query="page" type="number" default="1">
  1-indexed page number. Minimum `1`.
</ParamField>

<ParamField query="limit" type="number" default="50">
  Page size. Minimum `1`, maximum `100`.
</ParamField>

<ParamField query="direction" type="string" default="desc">
  Sort direction. One of `asc`, `desc`.
</ParamField>

## Response

```json theme={null}
{
  "items": [
    {
      "id": "5e1b…",
      "chat_id": "a196cec6-49cb-4c48-8e4c-2707fb5d6709",
      "chat_thread_id": "71562cd0-1f63-41b2-8382-10f2fdb1ac8b",
      "task_id": "0e7d…",
      "role": "assistant",
      "type": "final_message",
      "message": "Hi! Octopuses have three hearts — two pump blood to the gills and one to the rest of the body.",
      "ts": 1746038518,
      "created_at": "2026-04-30T18:21:42.000Z",
      "updated_at": "2026-04-30T18:21:42.000Z"
    }
  ],
  "meta": { "total": 1, "page": 1, "limit": 50, "total_pages": 1 },
  "_links": {
    "self": { "href": "https://api.blocks.team/rest/v1/sessions/a196cec6-…/messages?type=final_message&role=assistant" },
    "new_messages": { "href": "https://api.blocks.team/rest/v1/sessions/a196cec6-…/messages?type=final_message&role=assistant&gts=1746038518" }
  }
}
```

<ResponseField name="items" type="object[]">
  Page of messages.

  <Expandable title="message properties">
    <ResponseField name="id" type="string (uuid)">
      Message ID.
    </ResponseField>

    <ResponseField name="chat_id" type="string (uuid)">
      The session this message belongs to.
    </ResponseField>

    <ResponseField name="chat_thread_id" type="string (uuid) | null">
      The thread the message was sent on, if any.
    </ResponseField>

    <ResponseField name="task_id" type="string (uuid)">
      The task ID for the agent invocation that produced this message.
    </ResponseField>

    <ResponseField name="role" type="string">
      Sender role: `user` or `assistant`.
    </ResponseField>

    <ResponseField name="type" type="string">
      Message type: `message`, `final_message`, or `tool_call`.
    </ResponseField>

    <ResponseField name="message" type="string">
      The message body.
    </ResponseField>

    <ResponseField name="ts" type="number | null">
      Provider-supplied timestamp in epoch seconds. Use as the `gts` cursor for incremental polling.
    </ResponseField>

    <ResponseField name="created_at" type="string (ISO 8601)">
      When the message was created.
    </ResponseField>

    <ResponseField name="updated_at" type="string (ISO 8601)">
      When the message was last updated.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object">
  Pagination metadata.

  <Expandable title="properties">
    <ResponseField name="total" type="number">
      Total messages matching the filter.
    </ResponseField>

    <ResponseField name="page" type="number">
      The current 1-indexed page.
    </ResponseField>

    <ResponseField name="limit" type="number">
      The page size used for this response.
    </ResponseField>

    <ResponseField name="total_pages" type="number">
      Total page count for the current filter.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="_links" type="object">
  HATEOAS links for the page.

  <Expandable title="properties">
    <ResponseField name="self" type="object">
      Link to the current page (preserves all query parameters).
    </ResponseField>

    <ResponseField name="new_messages" type="object">
      Pre-built URL with `?gts=<cursor>` appended. Poll this URL to receive only messages newer than the latest one in this page. Always present — when `items` is empty, the cursor is the server's current epoch second, so the next poll returns anything created after now.
    </ResponseField>
  </Expandable>
</ResponseField>

## Polling tips

* For the assistant's first reply, use the `_links.final_message.href` returned from [`Create Session`](/rest-api/sessions/create) — it already has the right filters applied.
* For incremental polling, use `_links.new_messages.href` from the previous response. It encodes the `gts` cursor so each poll only returns new messages.

## Errors

| Status | Code        | Reason                                                                  |
| ------ | ----------- | ----------------------------------------------------------------------- |
| `404`  | `NOT_FOUND` | Session (or thread) does not exist or belongs to a different workspace. |
