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

# Messages

> What the user said and what the agent replied. Posting one starts a run.

A message has a `role` — `user` or `assistant` — and content. You post user messages; assistant messages are produced by [runs](/guides/runs).

```bash theme={null}
curl -X POST https://api.usepatchwork.co/v1/loom/threads/<thread_id>/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Patchwork-Subject: user_9f2" \
  -d '{"content": "Where is my order?"}'
```

The response is `202` with a `run_id` — the turn has started, not finished.

## Attaching files

To hand the assistant a file, [upload it](/guides/files) first and pass its handle in `file_ids`:

```bash theme={null}
curl -X POST https://api.usepatchwork.co/v1/loom/threads/<thread_id>/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Patchwork-Subject: user_9f2" \
  -d '{"content": "Here'"'"'s the invoice — pull the totals.", "file_ids": ["file_9f3a..."]}'
```

The assistant sees the file, and can pass its handle to a file-reading tool like [extract](/guides/extract) (wired as a patch). Attachments are PDFs or images, at most 10 MiB each, up to 10 per message. Files stay available for the life of the thread.

## Posting resumes a paused run

If the thread has a run waiting on a question, your message is the answer and that run continues. If it has no paused run, a new run starts. You do not choose between these; the runtime does.

The exception is a run waiting for a **write approval**. There, a message is refused:

```json theme={null}
{ "error": { "code": "awaiting_confirmation",
             "message": "This run is waiting for you to approve or reject a pending change." } }
```

Approve or reject first — see [Confirmations](/guides/confirmations).

## Reading them

`GET /v1/loom/threads/:thread_id/messages` returns the thread's messages in order. For what the agent *did* rather than what it said, read the [trace](/guides/traces) instead.

## Titles

The first user message becomes the thread's title, truncated to 80 characters, for your history list. It is set once and not revised.

## Streaming

If the agent has streaming enabled and [realtime](/guides/realtime) is configured, assistant content arrives as `message.delta` events during the run, then a final `message.completed`. Without realtime, poll the run and read the message when it closes.

## Rich messages

An assistant reply can carry structured data alongside its text. When it does, the message has a `parts` array — an ordered list of `text` parts and `data` parts. A data part is `{ "type": "data", "kind", "payload", "ref" }`: Patchwork sends the `kind` and a structured `payload`, and **you render it** — a chart, a table, a card, whatever your UI does with that kind. We never send markup or images.

```json theme={null}
{
  "role": "assistant",
  "content": "Here are your agents:",
  "parts": [
    { "type": "text", "text": "Here are your agents:" },
    { "type": "data", "kind": "agents", "payload": { "data": [ /* ... */ ] }, "ref": "fetch_agents@1.0.0" }
  ]
}
```

`content` is always the plain-text fallback, so a client that doesn't recognise a `kind` still shows something. A block reaches realtime subscribers as a `message.block` [event](/guides/events); on reload it is on the message itself.

Two things produce a data part: a patch tagged `render: { kind }` (its result becomes the block) and the native `render_*` tools the model can call. Rendering and any interactivity are yours — if a block needs to tell the agent something, just send a normal message.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.