> ## 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.

# Sources

> Files an agent holds, how to attach an image to a chat message, and how an answer points back to a file.

This page explains sources. After you read it, you can upload an image for a chat message, list and download the files an agent holds, and read a citation.

A **source** is a file that an agent holds. There are two kinds:

* A **document** that the agent learned from through [`observe-file`](/docs/concepts/learning). The upload response returns its `source_id`.
* An **image** that you attach to one chat message with `PUT .../sources/images`. The model looks at the image when it writes that one answer.

Sources belong to the agent. Every thread of the agent can point to them.

## Upload an image for a chat message

Send the image bytes with `PUT /v1/agents/{agent_id}/threads/{thread_id}/sources/images`. Set `Content-Type` to `image/png`, `image/jpeg`, or `image/webp`. The image can be up to 10 MB.

```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 @screenshot.png
```

The response has the same format as a citation:

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

Then send the `source_id` in `image_source_ids` on a [`chat`](/docs/concepts/answering) message. One message can name up to 10 images, with a total of 32 MB. `GET /v1/session` returns the limits that apply to your organization.

### Errors

| Status | `code` | Cause | What to do |
| - | - | - | - |
| `400` | `invalid_request` | The `Content-Length` header is missing or is not a number. | Send the header with the exact number of bytes. |
| `404` | `thread_not_found` | The thread or the agent does not exist, or you cannot open it. | Check the ids. |
| `413` | `payload_too_large` | The image is larger than 10 MB. | Send a smaller image. |
| `422` | `invalid_request` | The bytes are not a PNG, JPEG, WebP, or GIF that can be decoded. | Export the image again. |
| `429` | `rate_limit_exceeded` | More than 20 uploads in one minute. | Wait the number of seconds in `Retry-After`. |
| `503` | `service_unavailable` | Image storage is not available. | Wait, then try again. |

## List and download sources

| Call | What it does |
| - | - |
| `GET /v1/agents/{agent_id}/sources` | Lists every source the agent holds. Use `limit` and `cursor` to read page by page. |
| `GET /v1/agents/{agent_id}/sources/{source_id}` | Downloads the original file. |
| `GET /v1/agents/{agent_id}/threads/{thread_id}/sources/{source_id}` | Downloads a file that an answer in this thread pointed to. A citation's `href` is this path. |

### Errors

| Call | Status | `code` | Cause | What to do |
| - | - | - | - | - |
| list | `400` | `invalid_request` | The `cursor` is old or is not from this list. | Start again without a `cursor`. |
| list | `503` | `service_unavailable` | Storage did not answer. | Wait, then try again. |
| download | `404` | `not_found` | The agent has no source with this id, or you cannot open the agent or the thread. | Check the ids. |
| download | `503` | `service_unavailable` | Storage did not answer. | Wait, then try again. |

### Upload state on the list

For a document whose upload did not finish, each entry on the list also says where it stands. The fields are null when there is nothing unfinished.

| Field | Meaning |
| - | - |
| `upload_state` | `learning` (still running), `resuming` (it continues by itself after a Priors restart; nothing to do), `partial` (it stopped partway), or `failed`. |
| `stop_reason` | Why a `partial` or `failed` upload ended: `cancelled`, `learning_stopped` (a section could not be learned; finishing usually completes it), or `unreadable` (the file gave nothing to learn; try another copy or format). |
| `turns_done`, `turns_total` | How many pieces were learned, out of how many. |
| `can_finish` | Whether you can call `finish-learning` on it. See the next section. |
| `figures_unread` | How many pictures in the document could not be read, even when its text was learned. |
| `figures_retry` | `available` when you can ask for the pictures to be read again, `running` while that runs. |
| `parent_source_id` | For a picture cut from a document: the document it came from. |

## Finish an upload that stopped

When an upload stopped partway or failed, Priors keeps the file. Call `POST .../threads/{thread_id}/sources/{source_id}/finish-learning` to finish it. You send no file. Only the pieces that were not learned are read and charged.

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

The response is `202`, with the same body as `observe-file`. Poll the `observation_id` it returns. The upload resumes in the thread it was uploaded into, which can be a different thread from the one in the path. The response names the thread, and the `Location` header gives the status path.

To resume an upload in its own thread, you must be able to open that thread. `can_finish` on the list says whether you can. If you cannot, the call returns `409 conflict`. You can then send `{"here": true}` to read the whole file again in your thread. That stores a second copy, charged in full.

| Status | `code` | Cause | What to do |
| - | - | - | - |
| `409` | `conflict` | The upload did not stop or fail, or you cannot open the thread it was uploaded into. | Check `upload_state`. Or send `{"here": true}`. |
| `409` | `duplicate_upload_in_progress` | This file is being read now. | Do nothing. Poll the running upload. |
| `413` | a document code | The stored file is over a document limit that applies now. | Split the file and upload the parts. |

## Read unread pictures again

Sometimes a document's text is learned, but some of its pictures cannot be read. `figures_unread` on the list then counts them, and `figures_retry` is `available`. Call `POST .../threads/{thread_id}/sources/{source_id}/retry-pictures` to read only those pictures again. Priors never does this by itself.

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

The response is `202` with an `observation_id`. Poll it like an upload. Each picture that can now be read is learned as one short piece that cites the document. The text of the document is not read or charged again: the charge is for the words of the picture pieces only. A picture that still cannot be read costs nothing. The job adds nothing to the thread in the path. `409 conflict` means no pictures are left to read, or a retry of this file is already running.

## Citations

A [`chat`](/docs/concepts/answering) answer has a `citations` list. Each citation points to one source that the answer used. It has a `source_id`, a `label` to show to people, and an `href` on the same host that downloads the file.

The `verified` field tells you how the source reached the answer:

* `true`: the image was attached to this message, and the model looked at it.
* `false`: the file was learned earlier, and the answer used a memory that came from it. This is the normal case for documents.

`observe-and-respond` and `observe-and-enrich` do not return citations. They return `used_source_ids`. Use the sources list to find the file for each id.

## An image can also be a document

`observe-file` accepts an image file (PNG, JPEG, GIF, or WebP). Priors reads the format from the bytes, not from the file name, so a wrong extension is fine and a renamed file of another type is refused. Priors reads the text in the image, for example the text in a photographed receipt or a screenshot, and learns it like a one-page document.

This is different from attaching an image to a chat message. A chat attachment shows the image to the model for one answer. The agent learns nothing from the image itself.


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