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

# 4. Build a chat interface

> Receive answers piece by piece with chat/stream, attach images, show citations, and handle every event.

This walkthrough is for a chat screen built on Priors. After you read it, you can send one message with an image, show the answer while it is written, show the files the answer used, and handle every error.

`chat/stream` returns the answer as it is written, with citations at the end. This walkthrough sends one message with one image.

## Step 1: upload the image the user attached

```bash theme={null}
curl -X PUT https://app.sapientpriors.com/api/v1/agents/agent_01J.../threads/thread_01J.../sources/images \
  -H "Authorization: Bearer $SP_API_KEY" \
  -H "Content-Type: image/png" \
  --data-binary @error-screenshot.png
```

**Success:** status `200`.

```json theme={null}
{
  "source_id": "src_01J...",
  "label": "error-screenshot.png",
  "media_type": "image/png",
  "href": "/api/v1/agents/.../threads/.../sources/src_01J...",
  "verified": true
}
```

**If it fails:**

* `413 payload_too_large`: the image is larger than 10 MB. Make it smaller before you upload it.
* `422 invalid_request`: the bytes are not a PNG, JPEG, WebP, or GIF that can be decoded.
* `400 invalid_request`: the `Content-Length` header is missing or wrong. Send the exact number of bytes.
* `429 rate_limit_exceeded`: more than 20 uploads in one minute. Wait `Retry-After` seconds.

One message can name up to 10 images, with a total of 32 MB.

## Step 2: open the stream

```bash theme={null}
curl -N -X POST https://app.sapientpriors.com/api/v1/agents/agent_01J.../threads/thread_01J.../chat/stream \
  -H "Authorization: Bearer $SP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "turn_id": "3d0c1b7e-6a2f-4d8b-9c1e-5f7a9b3d1e2c",
    "message": "What does this error mean?",
    "image_source_ids": ["src_01J..."]
  }'
```

`turn_id` is an id that you create. It must be unique for each message. If the connection drops and you send the same `turn_id` again, Priors charges the message only once.

## Step 3: handle the events

The response is a stream of server-sent events.

```text theme={null}
event: stage
data: {"stage":"memory_context"}

event: delta
data: {"delta":"That error means the "}

event: delta
data: {"delta":"disk is full. "}

event: stage
data: {"stage":"final_generation"}

event: complete
data: {"turn_id":"3d0c...","message":"That error means the disk is full. ...","stages":[...],"citations":[...],"duration_ms":1790}
```

The table below says what to do with each event.

| Event | What to do |
| - | - |
| `stage` | Optional. `memory_context` means the memory search finished. `final_generation` means the answer is written. `turn_learning` means the message was learned. Show progress if you want. |
| `delta` | Add `delta` to the text on the screen. |
| `complete` | Replace the text on the screen with `message`. Show `citations`. Close the stream. |
| `error` | Show `error`. Close the stream. Use `code` to decide whether to try again. |

A stream always ends with exactly one `complete` event or one `error` event.

```javascript theme={null}
const res = await fetch(url, {method: "POST", headers: {"Content-Type": "application/json"}, body});
const reader = res.body.getReader();
// Read "event:" and "data:" lines. On "delta", append data.delta. On "complete", finish. On "error", show data.error.
```

## Step 4: show the citations

```json theme={null}
{
  "source_id": "src_01J...",
  "label": "error-screenshot.png",
  "media_type": "image/png",
  "href": "/api/v1/agents/.../threads/.../sources/src_01J...",
  "verified": true
}
```

Show `label` as a link to `href`. `verified: true` means the model looked at this file for this message, which is the case for the image you attached. `verified: false` means the link comes from a memory that the file produced earlier, which is the case for every document learned before. Both are real sources. You can style them differently.

## When it fails

Before the stream starts, the failure is an HTTP status:

* `400` or `422 invalid_request`: `turn_id` or `message` is missing, or `message` is longer than 2,000 characters. Correct it and send again.
* `413 payload_too_large`: more than 10 images, or more than 32 MB of images, in the message. Remove an image and send again.
* `404 not_found`: an id in `image_source_ids` is not an image this agent holds. Upload it first.
* `404 thread_not_found`: the thread is gone, or it is not yours.
* `402 payment_required`: no credits. Show a clear message. Do not send again.
* `409 agent_not_ready`: poll the agent, then send again.
* `429 rate_limit_exceeded`: more than 20 messages in one minute, or 2 answers already running. Wait `Retry-After` seconds. Show "one moment" to the user.
* `503 answer_model_unavailable`: wait, then send again with the same `turn_id`.

After the stream has started, the failure is the `error` event, with the same `code` values. Keep the text already shown. Offer the user a way to try again with the same `turn_id`.

## Step 5: show the history

```bash theme={null}
curl https://app.sapientpriors.com/api/v1/agents/agent_01J.../threads/thread_01J... \
  -H "Authorization: Bearer $SP_API_KEY"
```

This returns the thread with up to its 200 most recent messages. Show them when the user opens the conversation.

## What this walkthrough showed

* One message is one `turn_id`. Use the same `turn_id` when you send the message again.
* Upload images first, then name them in the message.
* Every message is also learned. The agent remembers the conversation.


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