Skip to main content
Messages belong to a chat. This endpoint returns the customer-visible transcript: user messages, assistant replies, tool calls, and tool results, in the order they occurred. Internal runtime parts (compaction, container uploads, hidden branches) and internal metadata (session IDs, model names, branching info) are never returned. Tool calls and results are normalized across transports — you’ll never need to branch on where a tool ran.

GET /v1/chats/:id/messages

List visible messages for a chat in chronological order (oldest first). Unlike /v1/chats and /v1/releases — which return newest-first — messages are ordered ascending so you can read a transcript top-to-bottom without reversing the list.

Request

Authorization
string
required
Bearer token. See Authentication.
id
string
required
Chat ID (UUID).
limit
integer
Number of messages to return. Default 20, max 100.
starting_after
string
Cursor for pagination. Pass the id of the last message from the previous page.
curl https://app.userjourneys.ai/api/v1/chats/7af6cf75-20ec-49f4-b113-929c01dfbe45/messages \
  -H "Authorization: Bearer uj_live_your_key_here"
{
  "object": "list",
  "data": [
    {
      "id": "a1b2c3d4-5678-90ab-cdef-111111111111",
      "object": "message",
      "chat_id": "7af6cf75-20ec-49f4-b113-929c01dfbe45",
      "role": "user",
      "parts": [
        { "type": "text", "text": "Why did onboarding drop this week?" }
      ],
      "sources": null,
      "feedback": null,
      "usage": null,
      "created_at": "2026-04-10T15:18:03.000Z",
      "completed_at": "2026-04-10T15:18:03.000Z"
    },
    {
      "id": "b2c3d4e5-6789-01ab-cdef-222222222222",
      "object": "message",
      "chat_id": "7af6cf75-20ec-49f4-b113-929c01dfbe45",
      "role": "assistant",
      "parts": [
        { "type": "thinking", "thinking": "Pulling the onboarding funnel..." },
        {
          "type": "tool_use",
          "id": "tool_q1",
          "name": "query_bigquery",
          "summary": "Weekly onboarding funnel",
          "input": { "sql": "SELECT step, count(*) FROM onboarding GROUP BY 1" },
          "state": "output_available"
        },
        {
          "type": "tool_result",
          "tool_use_id": "tool_q1",
          "content": { "rows": [{ "step": "signup", "count": 312 }] },
          "is_error": false
        },
        {
          "type": "text",
          "text": "The signup → verify step lost 18% compared to last week."
        }
      ],
      "sources": [
        {
          "id": "src_1",
          "description": "Weekly onboarding funnel",
          "rows": 312,
          "query": "SELECT step, count(*) FROM onboarding GROUP BY 1"
        }
      ],
      "feedback": "positive",
      "usage": {
        "input_tokens": 1420,
        "output_tokens": 362,
        "cache_read_input_tokens": 800,
        "cache_creation_input_tokens": 0,
        "cost_usd": 0.0087
      },
      "created_at": "2026-04-10T15:18:09.000Z",
      "completed_at": "2026-04-10T15:18:18.212Z"
    }
  ],
  "has_more": false
}
Returns 404 if the chat doesn’t exist, doesn’t belong to your project, or is excluded from the public chats API (internal, eval, or release-generated). Returns 400 with Invalid parameters when pagination inputs are malformed — for example, limit outside 1..100 or a non-UUID starting_after. The response body includes a details array with the Zod issue for each offending field. Returns 400 with No such message: <id> if starting_after parses as a UUID but is not a visible message in this chat.

Message object

id
string
Message ID (UUID).
object
string
"message"
chat_id
string
ID of the parent chat.
role
string
"user", "assistant", or "system". system is rare in public output.
parts
object[]
Ordered content blocks. See Part types.
sources
object[] | null
Citations referenced by an assistant message. null when there are none.
feedback
string | null
"positive", "negative", or null. User thumbs-up/down from the UI.
usage
object | null
Token and cost metrics for assistant messages. null on user/system messages or when metrics were not recorded.
created_at
string
ISO 8601 timestamp — when the message was recorded.
completed_at
string | null
ISO 8601 timestamp — when streaming finished. null while still streaming.

Part types

text

{ "type": "text", "text": "..." }

thinking

Internal reasoning shown to the user in a collapsible section.
{ "type": "thinking", "thinking": "..." }

redacted_thinking

Marker for reasoning that was redacted. No content.
{ "type": "redacted_thinking" }

file

User-uploaded file attached to a message.
{
  "type": "file",
  "url": "https://...",
  "media_type": "application/pdf",
  "filename": "brief.pdf"
}
filename is null if none was provided.

tool_use

A tool call made by the assistant. All tool transports (server tools, MCP tools) normalize to this single shape.
{
  "type": "tool_use",
  "id": "tool_abc",
  "name": "query_bigquery",
  "summary": "Weekly retention cohort",
  "input": { "sql": "SELECT ..." },
  "state": "output_available"
}
state
string
One of input_available, output_available, output_error, output_denied. Transient internal lifecycle states (e.g. streaming input, approval requests) are mapped to input_available.

tool_result

A tool result for a previous tool_use. All result transports (web search, code execution, MCP) normalize to this shape.
{
  "type": "tool_result",
  "tool_use_id": "tool_abc",
  "content": { "rows": [...] },
  "is_error": false
}

Tool payload stability

The shapes inside tool_use.input and tool_result.content are tool-specific and may evolve as tools change. Don’t hard-code field paths — read them defensively. Everything else in the response is stable and versioned.

Truncation

Large payloads are capped to keep response sizes predictable. When a tool_use.input, tool_result.content, or sources[].query exceeds 32 KB of JSON-encoded UTF-8:
{
  "type": "tool_result",
  "tool_use_id": "tool_abc",
  "content": null,
  "is_error": false,
  "truncated": true,
  "truncation_reason": "size_limit_32kb"
}
Check truncated === true rather than hardcoding the size — the threshold may change. Truncated sources surface the same way — query becomes null with matching truncated/truncation_reason fields:
{
  "id": "src_1",
  "description": "Weekly retention cohort",
  "rows": null,
  "query": null,
  "truncated": true,
  "truncation_reason": "size_limit_32kb"
}
The full content for truncated parts is not retrievable in this version.

Pagination

{ "object": "list", "data": [...], "has_more": true }
When has_more is true, pass starting_after with the last message’s id to get the next page. Results are ordered oldest first, so walking pages with starting_after traverses the transcript forward in time.