Documents and Images

Attach a file to a request and Paddock turns it into something the loaded model can actually read. Which path it takes depends on the model, and you can override the choice per request.

Two Routes, One Seam

A vision model with page rendering available gets the document's pages as images, so the vision tower reads the page the way you see it, layout and all. Every other combination gets the text route, because any model can read a PDF's text layer.

The practical effect is that a PDF is never refused for lack of a vision model. The one honest refusal left on the text route is a scanned document with no text layer, which has nothing to extract.

ParameterEffect
pdf_moderender forces page images, text forces extraction. Omit it for the automatic choice above.
max_pagesYour page cap for a multi-page attachment. The server's own limit still applies on top, and the lower of the two wins.
file_metadatafull (default) or off, which drops the metadata block described below.

These work identically on Chat Completions, the Responses API and Anthropic Messages. Token counting honors them too, so a count matches what generation will actually send.

What Each File Becomes

AttachmentWhat the model receives
PDF, vision modelRendered page images at a target long edge of 1568 pixels, which is the size vision towers are happiest with. Per-page resolution is derived from that and capped.
PDF, any modelLayout-preserving text per page, with a page marker for each one and an explicit note where a page has no text.
Word documents (.docx)The final text, with tracked changes resolved and the paragraph count in the header.
Spreadsheets (.xlsx, .xlsm, .xls, .xlsb, .ods)One Markdown table per sheet, with formula cells carrying their computed values.
ImagesRaw pixels for the vision tower, plus a one-line photo note when the file carries one. JPEG, PNG and AVIF decode; HEIC does not, and says so by name.
Text-native filesCode, CSV, JSON, Markdown and logs are inlined directly, after detecting the character encoding rather than assuming UTF-8.

Parsing is done by our own Rust libraries rather than by shelling out to the usual command-line tools or binding C++ ones. Attachment bytes arrive from wherever your users got them, and a memory-safe parser is the difference between a malformed file being an error and it being a problem.

The Shape In The Prompt

The injected text is deterministic and documented, because agents depend on prompt stability and a shape that drifts between releases breaks them quietly.

[Attached file: report.pdf - PDF, 3 pages]
Title: Q3 Report
Author: J. Smith
---
[page 1]
...layout-preserved text...

[page 2: no text]
...
[end of report.pdf]

Word Documents

A .docx comes in as its final text: tracked changes are resolved rather than left as a mixture of insertions and deletions that reads as gibberish to a model.

That resolution is never silent. When a document carries revisions, the prompt says so and says which reading it gave you:

[Attached file: contract.docx - Word document, 214 paragraphs]
Title: Supply Agreement
Author: J. Smith
Company: Acme Legal
[The document carries 37 tracked changes; the text below shows the final
version with all changes accepted.]

This matters for redlines in particular. A model asked to summarize a contract has no way to know it was handed the accepted-everything reading unless something tells it, and neither do you.

Spreadsheets

Every sheet in the workbook is extracted, in order, each as its own Markdown table under a header naming it and its dimensions. Excel, the older Excel formats, the binary format and OpenDocument all work.

[Attached file: budget.xlsx - Excel workbook, 3 sheets]

[Sheet: Q3 Actuals - 48 rows x 7 columns, starting at cell B4]
| Cost centre | Owner | Budget | Actual |
| --- | --- | --- | --- |
| Infrastructure | Ops | 120000 | 118400 |
...

Four decisions in there are worth knowing about:

  • Formula cells give their computed values. What you see in Excel is what the model reads, rather than a page of =SUM(B2:B48) that answers nothing.
  • The first row becomes the table header. Where a sheet has headers, that is what they are; where it does not, the data still shows faithfully rather than being dropped to make the format fit.
  • A sheet that does not start at A1 says where it starts. Without that, "row 3 of the table" and "row 3 of the sheet" quietly stop meaning the same thing, and any answer citing a cell becomes unverifiable.
  • Empty sheets are named, not skipped. A sheet missing from the prompt and a sheet with nothing in it are different facts.

If a workbook cannot fit the context window, the error names the sheet where it ran out rather than reporting a total that leaves you guessing which one is oversized. The same applies to a long document.

Metadata

PDFs contribute Title, Author, Subject, Keywords, and the created and modified dates, in that fixed order, and only when the document actually carries them. Producer, creator tool, page size and similar fields are deliberately dropped: they are noise no one asks a model about.

Office files carry more, and it is often the useful part. Alongside the same identity fields come the last person to modify the file, the company and manager, and the page and word counts as recorded by the writing application. Those counts are reported as the application's claim rather than as our own measurement, because that is what they are.

Custom document properties come through as well, capped so a file with a tag zoo cannot flood the prompt. These are where a corporate document quietly records its own history: document-management client and matter stamps, and the markers comparison tools leave behind. A model that can see them can answer questions about a document that its visible text does not contain.

Photos contribute a single line with the fields people actually ask about: when it was taken, the camera, and GPS coordinates when present. Orientation is deliberately not reported, because the pixels are uprighted during decoding and describing the stored rotation of an already-corrected image would be false. An image carrying none of these fields gets no line at all, so screenshots and plain PNGs produce byte-identical prompts.

AVIF Yes, HEIC No

AVIF and HEIC share one container and are nothing alike legally, which is why one works and the other cannot. AV1 has a permissively licensed decoder written in Rust, so it links into the binary with no library beside it and no extra step. Every HEVC decoder is under a copyleft licence, and embedding one in a closed binary would require us to ship every user relinkable object code, so we do not ship one.

The consequence is worth stating plainly because it is permanent rather than a gap: a HEIC is refused by codec, before anything tries to read its pixels, and the error names the codec. It does not suggest installing something, because there is nothing you could install. An iPhone writes HEIC by default, so this is a real limit rather than a theoretical one, and the honest answer today is to convert or to have the phone shoot JPEG.

Reading a file never rewrites it. A viewable rendition is derived for display, and metadata is read from the original bytes.

Where A Photo Was Taken

A photo carrying coordinates gets them turned into a place, and the default view for that contacts nothing at all. Coastlines and borders come from an outline bundled with the application and the place name from an offline table, so it works on a plane and, more to the point, glancing at your own photo does not tell a third party where you were.

A real slippy map is one button away, never automatic, and the host it will contact is named next to the button before you press it. The tile source is a setting, because a heavy user should point at their own server and anyone with a provider contract should use it. The default is a service that is explicitly free for commercial use and needs no key, which rules out the better-looking options whose terms would have quietly made every paying user an unlicensed one.

Checking Whether An Image Is What It Claims

An attached image or document can be run through signal-level forensics before the model sees it: compression history, sensor noise, splices, resampling, and whether a page was rendered or scanned. The findings are handed to the model as evidence to weigh rather than returned as a verdict. It is off by default and enabled per endpoint and per conversation. See Image Forensics.

Metadata Is Untrusted Input

A document's Title is attacker-controlled text heading into a prompt. Left alone, a Title containing newlines could forge page markers or invent extra metadata lines, and the model would have no way to tell the difference. Control characters therefore collapse to spaces and every line is length-capped, so a metadata value can only ever be one line saying what it says.

Truncation Is Visible

When a page cap trims a document, the response says so and reports how many of how many pages were used. A silently shortened document is a wrong answer that looks like a right one, so it never happens quietly.

Previewing An Extraction

POST /api/extract runs a file through the same lanes and returns what the model would receive, with the same file_metadata contract. Refusals come back as the same errors a real request would produce, so a preview never suggests a send will work when it will not.

Configuration

Page rendering needs a rendering library configured on the server; without it, PDFs take the text route instead. The library path, the server-side page cap and the target page size are all settings, covered in Server Configuration.