Beta
Bank Statement API
Send a bank statement PDF or scan to the FastOCR REST API and get the transactions back as JSON, CSV, Excel (XLSX), QuickBooks QBO, QuickBooks CSV, QFX and OFX. Every account is checked against its opening and closing balance. It uses the same pages as the bank statement converter on the website.
50 free statement pages, then Bank Statement Packs from $4.99 for 100 pages.
Three calls from PDF to spreadsheet
No start call, no webhook to host and no file size to compute. Polling the statement after the upload is what begins extraction.
- 1POST/v1/bank-statementsCreate the statement with its filename. The response has the upload_url.
- 2PUTupload_urlSend the file straight to the presigned URL. Nothing passes through the API gateway.
- 3GET/v1/bank-statements/{id}Poll until status is completed or failed. The first poll after the upload starts extraction, and a completed statement carries a download url for each format.
Download the file from the link in the completed statement. Links last one hour; GET the statement again for fresh ones. Concurrency: 2 documents at once; 10 with a contract or 10,000+ pages bought.
Code: statement PDF to Excel
Each example reads your key from FASTOCR_API_KEY and writes statement.xlsx. Change outputs.xlsx to any format below. Endpoint details are in the Bank Statement API reference.
cURL
export FASTOCR_API_KEY="your_api_key"
# 1. Create a statement
RESPONSE=$(curl -sS --fail-with-body -X POST https://api.fastocr.org/v1/bank-statements \
-H "Authorization: Bearer $FASTOCR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"filename":"statement.pdf"}')
STATEMENT_ID=$(echo "$RESPONSE" | jq -r .id)
UPLOAD_URL=$(echo "$RESPONSE" | jq -r .upload_url)
# 2. Upload the file to the presigned URL
curl -sS --fail-with-body -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @statement.pdf
# 3. Poll until the status is completed or failed. The first poll after the upload starts extraction.
while true; do
BODY=$(curl -sS --fail-with-body https://api.fastocr.org/v1/bank-statements/$STATEMENT_ID \
-H "Authorization: Bearer $FASTOCR_API_KEY") || exit 1
STATUS=$(echo "$BODY" | jq -r .status)
case "$STATUS" in completed|failed) break;; esac
sleep 3
done
[ "$STATUS" = "completed" ] || { echo "$BODY" | jq .error; exit 1; }
# Download the Excel file from the link in the completed statement
curl -sS --fail-with-body "$(echo "$BODY" | jq -r .outputs.xlsx.url)" -o statement.xlsxNode.js 18+
// Node.js 18+, no dependencies. Save as statement.mjs and run: node statement.mjs
import { readFile, writeFile } from 'node:fs/promises';
import { randomUUID } from 'node:crypto';
const BASE = 'https://api.fastocr.org';
const auth = { Authorization: `Bearer ${process.env.FASTOCR_API_KEY}` };
async function api(path, init = {}) {
const response = await fetch(BASE + path, { ...init, headers: { ...auth, ...init.headers } });
const body = await response.json();
if (!response.ok) throw new Error(`${response.status} ${body.error?.code ?? body.message}`);
return body;
}
const pdf = await readFile('statement.pdf');
// 1. Create a statement
const created = await api('/v1/bank-statements', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Idempotency-Key': randomUUID() },
body: JSON.stringify({ filename: 'statement.pdf' }),
});
// 2. Upload the file to the presigned URL
const upload = await fetch(created.upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'application/pdf' },
body: pdf,
});
if (!upload.ok) throw new Error(`Upload failed: ${upload.status}`);
// 3. Poll until the status is completed or failed. The first poll after the upload starts extraction.
let statement;
do {
statement = await api(`/v1/bank-statements/${created.id}`);
if (!['completed', 'failed'].includes(statement.status)) await new Promise((resolve) => setTimeout(resolve, 3000));
} while (!['completed', 'failed'].includes(statement.status));
if (statement.status === 'failed') throw new Error(statement.error.code);
// Download the Excel file from the link in the completed statement
await writeFile('statement.xlsx', Buffer.from(await (await fetch(statement.outputs.xlsx.url)).arrayBuffer()));
console.log(statement.status, statement.pages_billed, statement.needs_review);Python SDK
from fastocr_sdk import FastOCR
# Reads the key from the FASTOCR_API_KEY environment variable.
with FastOCR() as client:
job = client.extract_statement("statement.pdf")
client.bank_statements.save(job.id, "statement.xlsx", "xlsx")
print(job.status, job.pages_billed, job.needs_review)What a completed statement looks like
needs_review is false when the balance and source checks found no issue. When it is true, compare the result with the statement before you rely on it. Accounting formats list one link per account under files.
{
"id": "stmt_a1b2c3d4e5f6",
"status": "completed",
"pages_billed": 3,
"needs_review": false,
"outputs": {
"json": { "available": true, "url": "https://...", "expires_at": "2026-11-05T13:00:00Z" },
"csv": { "available": true, "url": "https://...", "expires_at": "2026-11-05T13:00:00Z" },
"xlsx": { "available": true, "url": "https://...", "expires_at": "2026-11-05T13:00:00Z" },
"qbo": {
"available": true,
"accounts": [1],
"files": [{ "account": 1, "url": "https://...", "expires_at": "2026-11-05T13:00:00Z" }]
}
}
}Output formats
| Key in outputs | File | Use it for |
|---|---|---|
| json | Structured data | Accounts, transactions, balances and per-row verification for your own code. |
| csv | Spreadsheet rows | One row per transaction, with a balance-check row for each account. |
| xlsx | Excel workbook | A Summary sheet, then one sheet per account. |
| qbo | QuickBooks Web Connect | One file per account, for QuickBooks import. |
| quickbooks_csv | QuickBooks CSV | Columns laid out for the QuickBooks CSV import. |
| qfx | Quicken | One file per account, for Quicken. |
| ofx | Open Financial Exchange | One file per account, for Xero and other accounting tools. |
One balance for the website and the API
The first 50 statement pages are free, whether you start on the website or with an API key. After that you buy a one-time Bank Statement Pack, from $4.99 for 100 pages. There is no subscription. Check what is left with GET /v1/balance. See API pricing.
Frequently asked questions
What does the bank statement API return?
The structured contents of the statement: accounts, transactions with dates, amounts and running balances, and a verification result for each row and account. You choose the file: JSON, CSV, Excel (XLSX), QuickBooks QBO, QuickBooks CSV, QFX and OFX. Accounting formats come as one file per account.
How do I convert a bank statement PDF to JSON with an API?
POST to /v1/bank-statements, PUT the PDF to the upload_url you get back, then GET the statement until its status is completed. The completed statement has a download url for the json output.
How does the balance check work?
FastOCR compares each account's opening balance, the extracted transactions and the closing balance. The JSON and CSV show whether each account reconciled, and needs_review is true when a person should compare the result with the source. It is a workflow signal, not a guarantee that every value is correct.
Does it read scanned statements, photos and card statements?
Digital PDFs with a text layer are the best supported input. Scanned statements and photos are read with OCR and may need more review. Loan statements are refused and not charged. Check needs_review on every result.
How much does the bank statement API cost?
The first 50 statement pages are free. After that, Bank Statement Packs start at $4.99 for 100 pages and are paid once, with no subscription. The API and the website share one statement balance, so a pack bought on either works on both.
How many statements can I process at once?
Bank statements follow the same limits as Document OCR: 2 documents at once; 10 with a contract or 10,000+ pages bought. A statement can have up to 100 pages.
Is the bank statement API stable?
The bank statement API is in beta, like the web converter. The six-call flow with a separate start call keeps working, so integrations built on it do not change.
Extract your first statement
Sign in and create an API key in the developer console. You accept the API terms with your first key.
Also: PDF OCR API · Bank statement to QuickBooks · Bank statement converter