Skip to main content
API Docs

Bank Statements

Upload a bank statement and extract transactions as structured data. Get JSON, CSV, or best-effort XLSX. Accepts PDF, PNG, JPG, JPEG, and WebP images up to 20 MiB; PDFs up to 50 MiB.

This API is in private beta and access is allowlisted per account. Request access before creating a key.

What does the bank statement API return?

The API returns statement and transaction data as JSON or CSV, with XLSX available on a best-effort basis. Its review fields report whether balance-consistency and source-grounding checks found an issue. A no-issue result can support controlled automation; a flagged result should be compared with the source by a person. The signal is not a certification or a guarantee that every extracted value is correct.

Quickstart

Same five-step flow as documents: authenticate, create, upload to the presigned URL, start, poll, and download.

quickstart.sh
FASTOCR_KEY="your-api-key"

# 1. Create a bank statement job
curl -s -X POST https://api.fastocr.org/v1/bank-statements \
  -H "Authorization: Bearer $FASTOCR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"filename":"statement.pdf","size_bytes":'$(wc -c < statement.pdf | tr -d ' ')'}'

# 2. Upload the file to the presigned URL (from step 1 response)
curl -s -X PUT "<upload_url>" \
  -H "Content-Type: application/pdf" \
  -T statement.pdf

# 3. Start extraction
curl -s -X POST https://api.fastocr.org/v1/bank-statements/<id>/start \
  -H "Authorization: Bearer $FASTOCR_KEY"

# 4. Poll until ready (status: completed or failed)
curl -s https://api.fastocr.org/v1/bank-statements/<id> \
  -H "Authorization: Bearer $FASTOCR_KEY"

# 5. Download as Excel
curl -s https://api.fastocr.org/v1/bank-statements/<id>/output?format=xlsx \
  -H "Authorization: Bearer $FASTOCR_KEY"

Accepted files: PDFs up to 50 MiB; PNG, JPG, JPEG, and WebP images up to 20 MiB.

Output formats: ?format=json, ?format=csv, or ?format=xlsx (best-effort Excel export).

Statuses: awaiting_upload processing completed or failed.

POST/v1/bank-statements

Create a new bank statement job and get a presigned upload URL.

Required headers

Idempotency-Key — a unique value per job. See Idempotency.

Request body

FieldTypeRequiredDescription
filenamestringYesMust end with .pdf, .png, .jpg, .jpeg, or .webp.
size_bytesintegerYesFile size. PDFs up to 50 MiB (52,428,800 bytes); images up to 20 MiB (20,971,520 bytes).
sha256stringNoHex-encoded SHA-256 checksum.
external_idstringNoYour own correlation ID.

Response 201

response
{
  "id": "stmt_a1b2c3d4e5f6",
  "status": "awaiting_upload",
  "upload_url": "https://s3.amazonaws.com/...",
  "expires_at": "2025-01-15T13:00:00Z"
}

GET/v1/bank-statements

List your bank statement jobs with optional filtering. See Pagination for query parameters.

Response 200

response
{
  "bank_statements": [
    {
      "id": "stmt_a1b2c3d4e5f6",
      "status": "completed",
      "external_id": null,
      "pages_billed": 3,
      "pages_total": 5,
      "has_transactions": true,
      "outputs": {
        "json": { "available": true },
        "csv": { "available": true },
        "xlsx": { "available": true }
      }
    }
  ],
  "next_cursor": null
}

GET/v1/bank-statements/{id}

Get the current status and metadata for a single bank statement job.

Response 200

response
{
  "id": "stmt_a1b2c3d4e5f6",
  "status": "completed",
  "external_id": null,
  "pages_billed": 3,
  "pages_total": 5,
  "has_transactions": true,
  "needs_review": true,
  "outputs": {
    "json": { "available": true },
    "csv": { "available": true },
    "xlsx": { "available": true }
  }
}

Response fields

FieldTypeRequiredDescription
idstringYesStatement job ID (starts with stmt_).
statusstringYesawaiting_upload, processing, completed, or failed.
external_idstring?NoYour correlation ID, if set on create.
pages_billedintegerNoPages charged against your quota.
pages_totalintegerNoTotal pages in the statement.
has_transactionsbooleanYesWhether transactions were extracted.
needs_reviewbooleanNoWorkflow signal from balance consistency and source-grounding checks. false means no issue was found by those checks; it is not a guarantee of correctness.
outputsobjectNoAvailable output formats. JSON and CSV are structured outputs; XLSX is best effort.
errorobjectNoPresent when status is failed. See Errors.

POST/v1/bank-statements/{id}/start

Start extraction after the file has been uploaded. No request body required.

Response 202

response
{
  "status": "processing"
}
  • 409 statement_not_startable — the job is not in a startable state.
  • 400 upload_not_found — the file has not been uploaded yet.
  • 400 size_mismatch — uploaded file size does not match size_bytes.
  • 429 concurrency_limit — too many jobs in flight. Retry after the Retry-After header (30s).

GET/v1/bank-statements/{id}/output

Get a presigned download URL for the extracted data.

Query parameters

FieldTypeRequiredDescription
formatstringYesjson, csv, or xlsx (best effort).

Response 200

response
{
  "format": "xlsx",
  "url": "https://s3.amazonaws.com/...",
  "expires_at": "2025-01-15T13:00:00Z",
  "pages_billed": 3,
  "pages_total": 5
}

The presigned URL expires in 1 hour. If the job is not yet complete, a 409 statement_not_ready error is returned. If the requested format is not available, a 404 output_not_found error is returned.

DELETE/v1/bank-statements/{id}

Delete a bank statement job and its files. Deletion is asynchronous — the job transitions to deleting and is cleaned up in the background.

Response 202

response
{
  "status": "deleting"
}

Returns 409 statement_processing if the statement is currently being processed.

Output Schema

When you download the JSON output (?format=json), you get a document object containing one or more accounts, each with its own transactions array. Multi-account statements (e.g. current + savings on the same PDF) are split automatically.

output.json
{
  "status": "completed",
  "needs_review": true,
  "page_count": 3,
  "accounts": [
    {
      "account_number": "12345678",
      "account_type": "savings",
      "bank_name": "Example Bank",
      "account_holder": "Jane Smith",
      "currency": "USD",
      "period": {
        "from": "01/06/2025",
        "to": "30/06/2025"
      },
      "opening_balance": 5000000,
      "closing_balance": 6234500,
      "needs_review": true,
      "verification": {
        "needs_review": true,
        "has_opening_anchor": true,
        "endpoints_tie_out": true,
        "chain_broken": false,
        "flagged_issues": [{ "row_index": 2, "type": "SOURCE_GROUNDING", "reason": "Amount needs comparison with the source." }]
      },
      "totals": {
        "credits": 3500000,
        "debits": 2265500,
        "net": 1234500,
        "transaction_count": 28
      },
      "transactions": [
        {
          "date": "02/06/2025",
          "time": null,
          "description": "Direct Deposit - Payroll",
          "reference": "PAY-2025-06",
          "type": null,
          "debit": null,
          "credit": 250000,
          "balance": 5250000,
          "page": 1,
          "verified": true
        },
        {
          "date": "05/06/2025",
          "time": "14:32",
          "description": "POS Purchase - Grocery Store",
          "reference": null,
          "type": null,
          "debit": 8750,
          "credit": null,
          "balance": 5162500,
          "page": 1,
          "verified": false
        }
      ]
    }
  ]
}

Amounts are integer minor units

All money fields (debit, credit, balance, opening_balance, closing_balance, and totals.*) are integers in the currency's minor unit (e.g. cents for USD, paise for INR). Divide by 100 to get the display value: 250000 = 2,500.00. This avoids floating-point rounding errors.

Document-level fields

FieldTypeRequiredDescription
statusstringYescompleted or no_transactions.
needs_reviewbooleanYesWhether a human should review the flagged items. true if any account needs review. See Verification below.
page_countintegerYesTotal pages in the uploaded file.
accountsarrayYesOne object per detected bank account.

Account fields

FieldTypeRequiredDescription
account_numberstring?NoAccount number extracted from the statement, or null if not detected.
account_typestring?NoE.g. savings, current, checking.
bank_namestring?NoName of the bank.
account_holderstring?NoAccount holder name.
currencystring?NoISO currency code (e.g. USD, GBP, INR).
periodobjectNo{ from, to } — statement date range as strings.
opening_balanceinteger?NoOpening balance in minor units.
closing_balanceinteger?NoClosing balance in minor units.
needs_reviewbooleanYesWhether a human should review this account's flagged items.
verificationobjectYesEvidence behind needs_review — see Verification below.
totals.creditsintegerYesSum of all credit amounts in minor units.
totals.debitsintegerYesSum of all debit amounts in minor units.
totals.netintegerYescredits minus debits in minor units.
totals.transaction_countintegerYesNumber of transactions extracted.
transactionsarrayYesTransaction objects for this account.

Transaction fields

FieldTypeRequiredDescription
datestringYesTransaction date as printed on the statement.
timestring?NoTransaction time, if present. null otherwise.
descriptionstringYesTransaction description / narration.
referencestring?NoReference or cheque number. null if not present.
typestring?NoTransaction type (e.g. ATM, POS, NEFT). null if not detected.
debitinteger?NoDebit amount in minor units. null if this is a credit transaction.
creditinteger?NoCredit amount in minor units. null if this is a debit transaction.
balanceinteger?NoRunning balance after this transaction in minor units. null if the statement does not print running balances.
pageintegerYesPage number where this transaction appears (1-indexed).
verifiedbooleanYestrue only when the row passes both the arithmetic-chain and source-grounding checks. false is a review signal. See Verification below.

Verification

Every extraction runs through arithmetic-chain and source-grounding checks. Both must pass for a transaction to be marked verified: true.

1. Balance chain reconciliation

Starting from the opening balance, the engine walks each transaction in order and verifies that previous_balance + credit - debit = current_balance using integer arithmetic (no floating-point). If the chain holds from the opening balance through to the closing balance, the account passes the arithmetic-chain check.

2. Source grounding

Each extracted amount is cross-checked against the raw OCR word positions in the original document. If an amount cannot be matched to source text at the expected position on the page, the transaction is flagged for review. This can identify values that need comparison with the original document.

needs_review

Every account — and the document as a whole — carries a boolean needs_review field.

needs_review: false

The configured balance and source-grounding checks found no issue that requires review. This is a workflow signal, not a guarantee; apply your own controls before automation.

needs_review: true

A balance chain or source-grounding check found an issue, or there was not enough balance data to check it. Check the verification object and route the affected items to human review.

The verification object

Each account carries a verification object with the evidence behind its needs_review value.

FieldTypeRequiredDescription
needs_reviewbooleanYesSame value as the account-level needs_review field.
has_opening_anchorbooleanYesWhether an explicit opening balance row was found on the statement.
endpoints_tie_outbooleanYesWhether the computed closing balance matches the printed closing balance.
chain_brokenbooleanYesWhether the running-balance chain broke at one or more rows.
flagged_issuesarrayYesObjects shaped { row_index?, page?, type, reason } for items that need review. Rows or pages are identified when available; broader document or account issues may also appear.

Per-transaction verified field

Each transaction carries a boolean verified field. It is true only when both the arithmetic-chain and source-grounding checks passed for that row. When processing results programmatically:

  • verified: true — the transaction passed the arithmetic-chain and source-grounding checks. It is a candidate for a no-review workflow under your own controls.
  • verified: false — a check failed or could not be completed. Flag this transaction for human review against the source document.

Tip: The document-level needs_review is true if any account needs review. If needs_review is true, check verification.flagged_issues for the row or page details when available. Some entries can describe broader document or account issues rather than one exact transaction row.

CSV & Excel Format

The CSV and Excel outputs flatten all accounts into a single table with a fixed column order. Multi-account statements include account_number and account_type columns so you can filter by account.

Columns

ColumnTypeNotes
datestringTransaction date as printed.
timestringEmpty if not present on statement.
descriptionstringTransaction narration.
referencestringReference/cheque number. Empty if not present.
typestringTransaction type (ATM, POS, NEFT, etc.). Empty if not detected.
debitnumberDebit amount. In JSON: integer minor units. In Excel: decimal (e.g. 87.50). Empty for credit rows.
creditnumberCredit amount. Same format rules as debit. Empty for debit rows.
balancenumberRunning balance after the transaction. Same format rules as debit/credit.
pageinteger1-indexed page number.
verifiedbooleantrue or false. See Verification above.
account_numberstringIdentifies which account this row belongs to in multi-account statements.
account_typestringE.g. savings, current, checking.
flagstringAlways the last column. Empty when nothing is wrong with the row; otherwise the problem types joined with +. best-effort means the row was read from the page but could not be math-checked, so it is returned with verified: false. In JSON the same information arrives as flag: [{ type, detail }], where detail says which check was unavailable: no-balance-column (the layout prints no running balance), chain, missing-balance, missing-amount, y-band, or dropped-sibling-row (another row on the same page could not be used, so nothing on that page is treated as checked).

CSV example

transactions.csv
date,time,description,reference,type,debit,credit,balance,page,verified,account_number,account_type,flag
02/06/2025,,Direct Deposit - Payroll,PAY-2025-06,,,,5250000,1,true,12345678,savings,
05/06/2025,14:32,POS Purchase - Grocery Store,,,8750,,5162500,1,false,12345678,savings,best-effort
06/06/2025,,ATM Withdrawal,,,20000,,,1,false,12345678,savings,best-effort

Excel workbook

The .xlsx file contains two sheets:

  • Transactions — same columns as the CSV. Money values are formatted as decimal numbers (not minor units). Rows where verified is false are highlighted with an amber background so they stand out for review. The sheet includes frozen headers and auto-filters.
  • Summary — document status, needs_review, and page count at the top, followed by a section per account with: account number, account type, bank name, account holder, currency, statement period, opening/closing balance, account needs_review, and computed totals (credits, debits, net, transaction count).

Note on amount formats: JSON uses integer minor units (e.g. 250000 = 2,500.00). CSV also uses integer minor units. Excel converts to decimal numbers with #,##0.00 formatting, so amounts display naturally (e.g. 2,500.00).