# Documents and media

Send PDFs, Office files, images, audio and video in a conversation; what each becomes, and what it costs.

## Documents

Any chat model can read a document: the API turns it into text before the model sees it, so the model needs no file support of its own. PDF, Word, Excel, PowerPoint, RTF, HTML, CSV, Markdown, JSON, XML and plain text are read.

| Route | Part | Holds |
| --- | --- | --- |
| `/v1/chat/completions` | `file` | A `filename`, and the file as `file_data` (a data URI, bare base64 or a URL) or `file_id` |
| `/v1/responses` | `input_file` | A `filename`, and the file as `file_data`, `file_url` or `file_id` |
| `/v1/messages` | `document` | A `source`: base64, a URL, text or a `file_id` |

```bash
# A document travels as a data URI; the API turns it into text the model reads
doc=$(printf '# Leave policy\n\nNew hires get 20 vacation days a year.\n' | base64 | tr -d '\n')

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": {"filename": "policy.md", "file_data": "data:text/markdown;base64,'"$doc"'"}}
    ]}]
  }'
```

```python
import base64
import os

from openai import OpenAI

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

# A PDF, Word, Excel or PowerPoint file works the same way: read its bytes and name its type
policy = base64.b64encode(b"# Leave policy\n\nNew hires get 20 vacation days a year.\n").decode()

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": {"filename": "policy.md", "file_data": f"data:text/markdown;base64,{policy}"}},
        ],
    }],
)
print(reply.choices[0].message.content)
```

The type comes from the data URI, then the filename's extension, then the file's first bytes. If none of them tells it, the answer is a 400 that asks you to name the file.

- The text counts toward the context window, and is billed as prompt tokens in every request that sends it.
- A file may be up to 100 MB, and a PDF up to 300 pages.
- A long PDF takes about half a second a page to read. If your request gives up partway, the conversion still finishes, and the same document sent within the hour is not read again.
- When the converter is busy the answer is 503; retry after `Retry-After`.

To use one document in many requests, upload it once to `POST /v1/files` and send its `file_id`: it is read once, up front. [Chat over your files](/docs/workflows/chat-over-files) shows the whole round.

## Images, audio and video

On `/v1/chat/completions`, a model that reads a kind of media gets it as it is. A model that does not gets a description of it, written by a model that does, so it can still answer about it.

| Media | Part | Takes | A model that reads it gets |
| --- | --- | --- | --- |
| Images | `image_url` | A data URI or an `https` URL | The picture, fitted to 768 by 768 pixels |
| Audio | `input_audio` | Up to an hour | The clip |
| Video | `video_url` | Up to two minutes | The clip, at one frame a second |

A model's catalog entry lists `audio` or `video` among its inputs when it hears or watches them itself. `/v1/responses` and `/v1/messages` pass media to the model as it is, without descriptions, so use a model that reads it.

## Errors

| Status | When |
| --- | --- |
| 400 | The file is unreadable, encrypted or empty, a PDF is over 300 pages, a URL could not be downloaded, or the type could not be told. The message says which. |
| 400 | A `file_id` that does not exist, is still processing, failed to convert, or has expired. |
| 503 | The converter is busy. Retry after `Retry-After`. |
