Why a packet, not just a file
Extraction is scoped to the packet, not to the individual files inside it. That means:- Cross-file context is preserved — parse and extract see the whole bundle at once, not one file at a time.
- Results come back at the packet level, as a single set of extracted fields for the whole document.
- Classify, split, and validate operate on the packet as one input.
Packets and files
The packet has an id; each file inside it has its own id too. You spend almost all your time working with packets — file ids only come up when you need to look at one specific file within a multi-file packet.
Two API names — one object. On v3 the packet is addressed natively: every request and response uses
document_packet_id (see the packet endpoints). The frozen v2 surface still calls the same object a file collection — file_collection_id, sometimes collection_id — used the same way. The ids are interchangeable across versions; mapping details in the migration guide.How you get one
You create a packet by uploading. Two ways to hand anyformat the bytes:- Direct upload (multipart) — send the file bytes in the request. The response comes back with
status: "uploaded"once the bytes are in storage; you can run immediately. Best when the bytes are on the user’s machine or in your app’s memory. - From a URL — give anyformat an HTTPS URL (a presigned S3 link, a hosted asset) and the server fetches the bytes for you. Best when the file already lives in object storage you can presign, or at a public URL — no need to stream it through your backend.
Two ways to create a packet. Upload with
POST .../upload/ (multipart, 1–10 files into one packet) or POST .../upload/from-url/ (1–10 HTTPS URLs, all-or-nothing; each file is named by its response’s Content-Disposition, else by the URL’s path). The URL import is atomic, so a 201 means the packet is fully in storage and there is no pending-fetch state to reason about.One file vs. multiple files
One file (the default). Every upload creates its own single-file packet. In everyday use the packet id and the file id are effectively interchangeable — you address the packet. Multiple files. Bundle them into one packet only when they belong together as a single document. The packet is then processed as a whole; extraction runs over the bundle, not file-by-file. If two files are unrelated, upload them as two packets — that keeps their results independent.Attaching metadata
Every v3 create path accepts an optionalmetadata field — a free-form JSON object stapled to the packet at creation time. There is no schema: the only validation is “must be a JSON object”. The value is echoed back verbatim on GET /v3/document-packets/{document_packet_id}/ and, when the packet is run, the extract node sees it inline in its prompt.
Two uses come with it out of the box:
- Carry structured context alongside the bytes. Anything you already know about the document — the vendor id you fetched it for, the account it belongs to, the batch you’re processing — travels with the packet without needing a side channel. Fetch the packet later and the same JSON comes back.
- Let extraction source a datapoint from your context. A top-level key whose name matches an extract-schema field is picked up: the LLM prefers the metadata value over anything it might read from the document. The extracted field’s evidence then carries the marker
text: "metadata.<key>"— a signal you can key on client-side to tell “sourced from caller metadata” apart from “sourced from the document” (see Runs & results → Evidence).
.../upload/ and .../upload/run/) — multipart can’t carry a nested object natively — and as a plain JSON object on the URL import (.../upload/from-url/).
What’s next?
Files
Supported formats, size limits, and per-file mechanics
Runs & results
What happens when you run a workflow on a packet, and the shape of what comes back

