The job
You receive a 60-page annual report as a PDF. You want the reporting period, total revenue and the auditor’s opinion as fields, the revenue-by-segment table as rows, and a reading of the key chart, without anyone paging through the file.Sample input
annual-report-2025.pdf, 60 pages:
- Pages 1–4: cover with the company logo, contents, and the chair’s letter with a signature.
- Pages 5–18: narrative sections with photos of sites and products, and a bar chart of revenue by quarter.
- Pages 19–44: financial statements. Dense tables with multi-level column headers (current year, prior year, and change, under each segment), subtotals, and footnote markers that point to notes at the foot of the page.
- Pages 45–56: notes to the accounts, mostly paragraphs and small tables.
- Pages 57–60: the independent auditor’s report, scanned from paper, with a signature and the firm’s stamp.
annual-report-2025.pdf
60 pages, 1.5 MB. The company, people and figures are made up.
Your output might be a little different from the Result below. Field names and the number of fields depend on the AI Schema in your workspace, but values such as the dates, totals and table rows should be close.
How fileScout processes it
Illustrative: the element breakdown is internal; the API returns the structured values.
Running headers and footers, such as the company name and page numbers on every page, carry no data. fileScout skips them, so they cost no tokens. Long files like this one are read in chunks; spreadsheets take a separate path of their own.
The fields you get back are the ones your AI Schema defines. The breakdown above is how fileScout finds them; it is not part of the response.
Steps
The cURL commands use two environment variables:1
Request an upload URL
Send the file name, its MIME type in Keep
fileType, and enabledProducts: ["FILE_SCOUT"]. fileScout needs normal processing, so leave storageOnly out. See Choose products for the rules.uploadId and presignedUploadURL from the response. enabledProducts works only on this single upload route and in workspaces enabled for it; otherwise the request returns 422.2
PUT the file
The URL is valid for 300 seconds. Send the same
Content-Type you gave as fileType.3
Wait for processing
Poll The report is one document, so
GET /v1/uploads/{uploadId} until every entry in files is processed or action_required, or wait for a processing callback. A 60-page file takes longer than a one-page invoice, so poll patiently: in our test run, the sample file took about 7 minutes.files has one entry. If you upload with isSplit: true, one file can become several documents; iterate over files in that case.4
Read the values, with every table row
Pass
includeFullTableData=true so tables over 100 rows come back whole.Result
Captured from a real run of the sample file in October 2026, with ids anonymised. The response had 106 fields; five are shown here. Your output might be a little different: the fields come from the AI Schema in your workspace, and extracted values can vary slightly between runs.
GET /v1/files/{fileId}/values?includeFullTableData=true
- Field titles come from the schema. This workspace had no schema for annual reports, so fileAI generated one from the file, with snake_case titles such as
group_revenueand adescriptionfor each field. Your own AI Schema decides the titles you get back, so look fields up by the titles you defined. - Scalar fields carry their answer in
value.group_revenueis a JSON number: the printed412,850,000is read as412850000. Reports often print figures in thousands or millions, so say in the field’s description which unit you want. See Extraction prompts. audit_opinion_typeis anenumfield. Itsvalueis always one ofenumValues, hereunqualified.revenue_by_segmentis alistfield. Itsvalueisnull, its columns are described initems, and its rows are invalueArray, each row an array of cells with their owntitle,typeandvalue. The two-level header (Revenue, then 2025, 2024 and Change) comes back as flat columns such asrevenue_2025andrevenue_change_percent.valueS3Uripoints to fileAI’s stored copy of the table; read the rows fromvalueArray. See Tables.- Printed totals come back as rows. The table’s Total line is the third row, with
segmentset tototal. Drop it before you add up the segments. - Printed formatting is cleaned up. Footnote markers such as
Retail (1)come back asretail, and+7.4%comes back as the number7.4. Amounts printed in brackets come back negative: the balance sheet’s(186,130,000)total liabilities is-186130000. - Empty cells come back as
null, and so does a number that is0, in fields and in table cells. Treatnullin a number field as zero or missing according to your own rules. See Data types. - No field describes the bar chart, because the schema didn’t ask for one. The quarterly figures came back from the printed table on page 29 instead, as
segment_revenue_by_quarter. If you need a reading of a chart, add a field for it to your schema. fileNamecan differ from the name you uploaded. The file was uploaded asannual-report-2025.pdfand came back asannual_report_2025.pdf. Match results to uploads byfileId.
What it shows
- One upload handles a long, mixed file: tables, text, charts, photos and scanned pages are each read on their own path, so you don’t pre-sort pages.
- Page furniture is skipped, so running headers and footers never turn into fields.
- Long tables need
includeFullTableData=true. Without it a table over 100 rows returns its first 100 and no error. - A chart is only described when your schema has a field for it. When you need exact figures, take them from a printed table.
Next steps
AI Schemas
Define the fields and tables fileScout fills.
Data types
Tables,
valueArray and how values are typed.Bank statement extraction
Long tables and a totals check.
Uploads
Every upload option, including
enabledProducts.