The job

A customer sends a 12-page monthly bank statement. You want the account details and balances as fields, and every transaction as a row, even when there are hundreds of them.

Sample input

A 12-page PDF, statement-2026-09.pdf. Page 1 shows the bank’s name, the account holder, the account number, the statement period, the opening balance and the closing balance. Pages 1 to 12 hold a running transactions table (date, description, debit, credit, balance) with 240 rows.

Steps

The cURL commands use two environment variables:
See Regions for regional hosts.
1

Upload the statement

Request an upload URL, then PUT the file to it within 300 seconds, as in the financial report example. See Uploads for every option.
2

Wait for processing

Poll GET /v1/uploads/{uploadId} until the documents in files are processed, or wait for a processing callback. Take the fileId from files.
3

Read every row

Tables longer than 100 rows are truncated unless you ask for the full table. Pass includeFullTableData=true:
4

Check the totals

Compare the extracted balances with the transactions before you load them: opening balance, plus credits, minus debits, should equal the closing balance.
Python

Result

Illustrative: shape from the API reference. Field and column titles depend on the bank statement schema in your workspace. Rows are shortened here.
GET /v1/files/{fileId}/values?includeFullTableData=true
  • valueArray holds every transaction row when you pass includeFullTableData=true. Without it, a table over 100 rows returns only its first 100.
  • The response is formValues (one entry per schema), with count and currentPage alongside.
  • Empty cells, such as Credit on a debit row, are null. A number that is 0 also comes back as null, so treat null as zero when you sum amounts. See Data types.
  • Grouped amounts in the document, like "18,250.40", come back as JSON numbers.
page and limit on GET /v1/files/{fileId}/values page through form values (one per schema), not table rows. There is no row-level pagination: use includeFullTableData=true to get the whole table in one response.

What it shows

  • Large tables need includeFullTableData=true. Without it you get the first 100 rows and no error.
  • A totals check catches a missed or misread row before it reaches your books.
  • The response nests rows as arrays of cells; flatten them by column title before loading.
To get the statement into Xero, download it from the app as XERO_CSV and import it there. See Xero export.

Next steps

Data types

Tables, valueArray and how values are typed.

AI Schemas

Adjust the statement fields and columns.

Xero export

Download bank statements as XERO_CSV.

Multi-page financial report

The full upload and polling flow, step by step.