# Chat over your files

Ask questions about your documents: upload them once and send them whole, or store them in a vector store and let the API find the passages that matter.

There are two ways, and the size of your documents decides between them.

| | Send the file | Search a vector store |
| --- | --- | --- |
| Fits | A few documents that fit the model's context window | More text than a context window holds |
| The model reads | The whole document, every request | Up to four passages that match the question |
| Billed | The document's text as prompt tokens, every request | The passages as prompt tokens |

## Send the file

Upload the document to `POST /v1/files`. It answers at once, and the text is read in the background:

<div class="flow" role="img" aria-label="processing, then processed, or error with the reason in status_details">
<span>processing</span><span>processed</span>
</div>

Or `error`, with the reason in `status_details`. Once processed, send its `file_id` in a `file` part of any chat request, as often as you like; it is never read again.

```bash
# 1. Upload the document; it is converted to text once, in the background
printf '# Leave policy\n\nNew hires get 20 vacation days a year.\n' > policy.md
file_id=$(curl -s https://api.vultrinference.com/v1/files \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY" \
  -F file=@policy.md | jq -r .id)

# 2. Wait until it is processed (or error, with the reason in status_details)
while [ "$(curl -s https://api.vultrinference.com/v1/files/$file_id \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY" | jq -r .status)" = processing ]; do sleep 1; done

# 3. Ask about it by its id, in as many requests as you like
curl https://api.vultrinference.com/v1/chat/completions \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "deepseek-v4.1-flash",
    "messages": [{"role": "user", "content": [
      {"type": "text", "text": "How many vacation days do new hires get?"},
      {"type": "file", "file": {"file_id": "'"$file_id"'"}}
    ]}]
  }'

# 4. Delete it when you are done (otherwise it expires after 30 days)
curl -X DELETE https://api.vultrinference.com/v1/files/$file_id \
  -H "Authorization: Bearer $VULTR_INFERENCE_API_KEY"
```

```python
import os
import time

from openai import OpenAI

client = OpenAI(base_url="https://api.vultrinference.com/v1", api_key=os.environ["VULTR_INFERENCE_API_KEY"])

# 1. Upload the document; it is converted to text once, in the background
uploaded = client.files.create(file=("policy.md", b"# Leave policy\n\nNew hires get 20 vacation days a year.\n"), purpose="user_data")

# 2. Wait until it is processed (or error, with the reason in status_details)
while (file := client.files.retrieve(uploaded.id)).status == "processing":
    time.sleep(1)
if file.status != "processed":
    raise SystemExit(f"{file.id}: {file.status_details}")

# 3. Ask about it by its id, in as many requests as you like
reply = client.chat.completions.create(
    model="deepseek-v4.1-flash",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "How many vacation days do new hires get?"},
            {"type": "file", "file": {"file_id": file.id}},
        ],
    }],
)
print(reply.choices[0].message.content)

# 4. Delete it when you are done (otherwise it expires after 30 days)
client.files.delete(file.id)
```

- A file may be up to 100 MB, a PDF up to 300 pages; a subscription holds up to 1,000 files.
- A file expires 30 days after upload, and a request that names it after that is a 400 that says so. Delete it sooner with `DELETE /v1/files/{id}`.
- The same `file_id` works in an `input_file` part on `/v1/responses` and a `document` part on `/v1/messages` ([Documents and media](/docs/concepts/documents)).

## Search a vector store

A vector store keeps your text as passages, each with an embedding, so the passages nearest a question can be found in a collection of any size.

1. **Make a collection** with a `name`. Its `id`, which every other call takes, is the name lowercased, cut to 14 letters, digits and underscores.
2. **Add text** as items, or upload whole files and let the API cut them into passages.
3. **Search** with an `input`, for the 10 nearest passages, nearest first.
4. **Or ask** the RAG route, which searches with the first user message and gives the model the four nearest passages before it answers.

| Step | Route |
| --- | --- |
| Make a collection | `POST /v1/vector_store` |
| Add text | `POST /v1/vector_store/{id}/items` |
| Add a file | `POST /v1/vector_store/{id}/files` |
| Search | `POST /v1/vector_store/{id}/search` |
| Ask | `POST /v1/chat/completions/RAG` |

An item holds up to 2,000 characters. Text longer than the embedding model reads at once is refused, unless you send `auto_chunk: true` to split it into overlapping passages. A file may be PDF, Word, Excel, PowerPoint, CSV, HTML, Markdown, AsciiDoc or plain text, and is `enqueued`, then `processing`, then `complete` or `error`.

```python
import os
import secrets

import requests

API = "https://api.vultrinference.com/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['VULTR_INFERENCE_API_KEY']}"


def call(method: str, path: str, **kwargs) -> dict:
    response = session.request(method, API + path, timeout=300, **kwargs)
    response.raise_for_status()
    return response.json() if response.content else {}


# 1. A collection; its id is the name lowercased, letters, digits and _ only, 14 characters at most
collection = call("POST", "/vector_store", json={"name": "handbook-" + secrets.token_hex(2)})["collection"]["id"]

# 2. Add text (up to 2,000 characters an item), or upload whole files to /vector_store/{id}/files
for text in ["New hires get 20 vacation days a year.", "Expenses over $500 need a manager's approval."]:
    call("POST", f"/vector_store/{collection}/items", json={"content": text})

# 3. Search it yourself: the nearest items first, 10 at most
for result in call("POST", f"/vector_store/{collection}/search", json={"input": "time off"})["results"]:
    print("found:", result["content"])

# 4. Or let the chat route search it with the first user message and answer from what it finds
reply = call("POST", "/chat/completions/RAG", json={
    "model": "deepseek-v4.1-flash",
    "collection": collection,
    "messages": [{"role": "user", "content": "How many vacation days do new hires get?"}],
})
print(reply["choices"][0]["message"]["content"])

call("DELETE", f"/vector_store/{collection}")
```

> [!NOTE]
> The RAG route searches with the first user message, as plain text: send the question as a string, first. It does not read `file` parts or media; use the ordinary chat route for those.
