Skip to main content
Available as an add-on on paid plans.
Tasks can produce files like receipts, invoices, statements, reports, images, and spreadsheets. When storage is enabled on a task, Deck captures the files the agent is instructed to collect and makes them available through the API. If extraction is also enabled, Deck parses supported files and returns structured JSON alongside the raw file.

Enabling storage on a task

Storage is configured when you create or update a task. Set storage.enabled to true to capture files. Set storage.extraction to true to also extract structured data from those files.
Captured files appear in storage, not the output schema. Don’t add fields like file names or file counts; they duplicate what storage returns and can drift from what was actually captured. The storage object is the source of truth for captured files and downloads.

Retrieving storage items

Pass ?include=storage when you fetch a task run and its captured files come back embedded in the response, each with its extracted data and a pre-signed download url inline:
The embedded array returns up to 100 items per run. To fetch every file, or to fetch a single item by ID, use the dedicated storage endpoints. The list endpoint returns all of a run’s files in one response:

Storage item fields

string
Unique identifier, prefixed with stor_.
string
Original file name as it appeared on the source.
string
MIME type (application/pdf, image/png, text/csv, etc.). See Supported files for supported types.
integer
Size in bytes.
string
attachment for a file you provide as task input for the agent to use, extraction for a file you provide as task input that Deck extracts data from directly (skipping the agent), or output for a file the agent captures during the run.
datetime
When the storage item was created.
object or null
Structured data extracted from the file, if extraction is enabled.
string
Extraction outcome: failure if the file’s data couldn’t be extracted, otherwise success (including files with no extraction). Returned by the list and get-by-id endpoints.
string
Signed download URL.
object or null
A JSON object of additional key-value details Deck records about the file, or null when there are none. Which keys appear depends on the file and how it was captured, so treat it as informational and don’t depend on specific keys being present. Returned by the list and get-by-id endpoints.
string
The task run that produced this storage item.

Downloading files

Both list and get-by-id responses include a pre-signed url you can use to download the raw file. URLs expire after an hour; if one does, re-fetch the item to get a fresh one.

Supported files

These rules apply to every file Deck stores: files an agent captures during a run (purpose: "output"), files you provide as task input (attachment), and files Deck processes directly (extraction).

File types

The following types can be captured and returned through storage. The Extraction column shows which can also be parsed into structured JSON when extraction is enabled.
When extraction is enabled, every captured file is sent for extraction, including types the extractor can’t parse. A captured GIF, BMP, or ZIP fails extraction, which fails the run with an extraction_failed error. If a task with extraction enabled might encounter these types, instruct the agent not to collect them.

File size

Each captured file can be up to 200 MB. Files you provide as task input have a smaller limit. See Sending a file.

Providing files as input

Tasks can accept files as input. The file is uploaded to storage and malware-scanned, then either handed to the agent or extracted directly by Deck, depending on the field’s purpose: Both purposes share the same field shape and upload behavior, and differ in what happens after the upload. A single task can declare attachment file inputs or extraction file inputs, not both. Running a task whose input schema mixes both is rejected with an input_invalid error.

Defining the field

Define a file field in the input schema. The shape is the same for both purposes; only the purpose constant changes.

Sending a file

Provide the file inline as base64 in the task run input:
Each file can be up to 30 MB. Larger files, or invalid base64, are rejected synchronously with a validation error and the run isn’t created. The content_type must be one of the supported types and match the actual file contents; a file that fails this check fails the run with a task_failed error. After upload, every file is malware-scanned asynchronously. The rest of the run lifecycle depends on the purpose.

Attachments

With purpose: "attachment", the agent receives the file at run time and uses it on the source: uploading it to a portal, attaching it to a form, or referencing it while completing the task. The run stays queued until every attachment passes the scan, then transitions to running and dispatches to the agent. If a file is flagged, the run fails with an attachment_invalid error on the task run object. Listen for task_run.failed or fetch the run to handle it. Deck replaces the base64 data in the stored input with a storage_id reference so the raw bytes aren’t carried through the run. The file becomes a storage item with purpose: "attachment", alongside the output files the agent captures, and appears under the Input tab on the task run in the Console.

Extraction

With purpose: "extraction", Deck processes the file directly against the task’s extraction_schema and returns the structured result on the run. The agent doesn’t execute. Use this when you have a document and want JSON back, with no source interaction. The run transitions to running as soon as the file is uploaded, and finalizes when extraction completes. It doesn’t sit in queued waiting on the scan. The task must have storage and extraction enabled, with an extraction_schema defining the result shape:
To run it, send the file in the extraction field. The run still needs a credential_id or source_id to link the extraction to a user or source, even though the agent doesn’t execute.
A task accepts one extraction input per run. The file becomes a storage item with purpose: "extraction" carrying the extracted data, matching the fields defined in extraction_schema. See Document extraction for guidance on writing extraction schemas.

Reusing a file across runs

Once a file is uploaded, you can reference it on a later run instead of sending the bytes again. Pass the storage_id in place of data, keeping the same purpose:
The same shape works with purpose: "extraction". Deck verifies the file belongs to your organization, then copies it for the new run so each run keeps its own input files. The copy is malware-scanned like a fresh upload. If the original has been deleted, by you or by retention, the reference is rejected with a resource_not_found error.

Document extraction

When extraction is enabled, Deck parses the captured files and populates the extraction field on each storage item with structured JSON. Extraction is available for PDF, CSV, Excel (.xlsx, .xls), plain text, JSON, and images (PNG, JPEG, WebP, TIFF). The extracted data depends on the document. A utility bill produces different fields than a hotel receipt. Extraction is powered by a language model, not rule-based or positional parsing. Deck packages your schema’s field names and descriptions with the document and sends them to the model, which matches document content to your fields by meaning rather than exact keywords: a start_date field with a clear description will match a label like “Starting date” even though the text differs. Values are the model’s best-fit reading of the document, not verbatim key lookups. If the model matches the wrong value, sharpen the field’s description or correct it with an extraction prompt. A file appears in storage once Deck finishes processing it. With extraction enabled, that means a file isn’t listed until its extraction succeeds or fails; when it appears, its extraction data is already populated.

Custom extraction schemas

Use the extraction_schema field on the task’s storage config to define exactly what fields you want extracted. Because the model matches fields by name and description, describe each field precisely, the same way you would for deduplication fields: "Total amount due, including tax" works better than a bare total.
Schemas are plain JSON Schema: a root object with properties, using types string, number, integer, boolean, object, array, and null. Composition and reference keywords ($ref, oneOf, allOf, anyOf, not, if/then/else, patternProperties, additionalProperties, definitions, $defs, $id, $schema) aren’t supported and are rejected with a validation error.

Guiding extraction with a prompt

extraction_prompt is optional free-text guidance applied alongside the schema. Set it on the task’s storage config to give the extraction model instructions that the schema’s field names can’t capture on their own: how to disambiguate similar fields, how an ambiguous printed value should be read, such as a day-first date, where on the document to look, what to assume when a value is missing, or fixes for mistakes you’ve seen it make.
The prompt steers how values are interpreted but doesn’t change the output shape, which is defined entirely by extraction_schema. It’s configured once on the task and applies to every file that task extracts. When omitted, extraction runs on the schema alone. Use it to interpret the document, not to transform the result. Deriving values, computing totals, and mapping to your own codes belong in your code, where they’re deterministic and testable. A value the prompt has reshaped no longer reconciles against the document it came from.

Extraction example

A utility bill extraction might return:

Extraction errors

Each storage item reports its extraction outcome in a result field: failure if the file’s data couldn’t be extracted, otherwise success. Note that success also covers files with no extraction, so it doesn’t on its own mean data was extracted. When extraction fails, result is failure and extraction stays null, but the raw file is still kept and available for download. Check result to tell which files failed. If any file in a task run fails extraction, the run completes with a failure result and an extraction_failed error in the errors array indicating how many files were affected. Successfully extracted files in the same run still return their extraction data. Failed extractions aren’t retried; run the task again to re-extract the file.

Deduplication

Deduplication tells Deck to skip files that match one captured by a previous run for the same task, source, and credential, so recurring tasks only return new documents. You define a set of fields that uniquely identify a document. Deck reads those fields from each captured file and compares them against prior captures. If every field matches, the new file is skipped: it’s not stored, no storage.created event fires, and it’s not extracted even if extraction is enabled. Each skip is recorded on the task run, so you can always tell a deduplicated file apart from one the agent never captured. See Seeing skipped files.

Configuration

Set deduplication to true and provide a deduplication_schema on the task’s storage config:
Each property must declare a type, one of string, integer, number, or boolean. Nested objects and arrays aren’t supported, so pick top-level scalar fields. Property names are arbitrary; you make them up, and they’re just keys for the result. The description is what tells Deck where to find the value on each document, so describe each field precisely. For example, "Account number printed at the top of the bill" works better than a vague "account".

Choosing fields

The fields you list together form the dedup key. Two files match only if every field is identical. String values are compared case-insensitively, ignoring surrounding whitespace. A few rules of thumb:
  • Pick fields that stay stable for the same logical document. A monthly bill should have the same account number and billing period every time it’s fetched.
  • Avoid volatile fields. File names, fetch dates, and page numbers will produce false negatives, since the same document looks new every time.
  • Pick enough fields to be unique. A single field like vendor_name will collide across unrelated invoices from the same vendor.
  • Mind reissued documents. A corrected or reissued bill shares its account number and billing period with the original, so a key of only those two skips the correction as a duplicate.
  • Two or three fields is usually right.
To let corrections through, add a printed document number to the key. It normally differs between an original and its correction, and it extracts identically on every run. Check a sample of your provider’s documents first: some utilities reprint the original number on a correction, and where that happens the correction still reads as a duplicate. Where a document type prints no unique identifier, as with many receipts, fall back to the most stable printed scalars available: the account or merchant, the period or transaction date, and the amount. The amount is then what separates a correction from its original, so a reissue that restates the same total is still skipped.

Field combinations by document type

Statement number plus account, so a corrected reissue still comes through:
The account number is included because statement numbers aren’t always unique across accounts.

Seeing skipped files

A skipped file never appears in storage, but the skip itself is recorded on the task run. Pass include=storage_deduplicated when you fetch the run to see which files were deduplicated and what they matched:
storage_deduplicated is a sibling of storage in the response. The two flags compose, but neither requires the other; include=storage alone never returns storage_deduplicated.
The field appears only when you request it and the run’s task has deduplication enabled:
  • A dedup-enabled run with no skips returns "storage_deduplicated": []. The empty array is meaningful: deduplication ran and nothing was skipped.
  • A run whose task doesn’t use deduplication omits the field entirely, even when requested.
Entries are inline values, not storage items. They have no id, no object type, and no endpoint of their own; the task run embed is the only place they appear.
string
Name of the skipped file as the agent captured it.
string
Storage ID of the file it matched, the one already in storage. Fetch GET /v2/storage/{duplicate_of} to get the original’s file name, extraction, and a fresh signed download URL. The entry doesn’t embed the original’s url or file_name: signed URLs expire, and the original can be deleted by retention, so the ID is the reliable reference.
string
The task run that originally captured the matched file. This is not the run you’re looking at; it’s the earlier run that first retrieved the document.
datetime
When the file was skipped.
The matched file follows the normal retention schedule. After it’s deleted, the entry keeps its duplicate_of value but the ID no longer resolves; task_run_id still points to the original run. Skips are recorded going forward only: runs from before this field existed have nothing to show.

Errors

deduplication_schema must be present with at least one property whenever deduplication is true. Starting a task run without it returns:
To disable deduplication, set deduplication to false (or omit it entirely).

Events

Storage items emit events you can subscribe to through event destinations: With extraction enabled, the event fires after extraction finishes, so the item’s extraction data is already available when you fetch it.

Retention

Retention period varies by plan. All files are deleted after 90 days.