Bank statement parser API
Parse any bank statement, no parser to write
Send a PDF from any bank and get clean transactions back as JSON, with a verdict that tells you the parse is complete. No per-bank code to write, and none to maintain.
Free test key, no card · Python, Node or any HTTP client · SOC 2 Type II
- statements parsed in the last 30 days
- 138,000+statements parsed in the last 30 days
- scanned pages in the last 30 days
- 188,000+scanned pages in the last 30 days
- field-level accuracy
- 99.9%field-level accuracy
Everything a statement parser has to get right
Statements look alike until you parse the hundredth bank. These come back right with no bank-specific code on your side.
Layouts you have never seen
Any bank, any statement design, with nothing to configure. A new bank is not a new parser.
Descriptions that wrap
A payee and memo printed over two or three lines come back as one transaction.
Debit, credit and balance columns
However the statement prints money in and money out, each row comes back with one amount and its running balance.
Dates without a year
Lines that print only the month and day come back as full ISO dates.
Several accounts in one PDF
Combined statements keep each account's rows apart, tagged by account.
Scans and long files
Scanned and photographed pages are read too, and files of hundreds of pages are converted in full.
Parse a bank statement in Python
Runs as-is with a free test key against our sample statements. Swap in a live key and an upload to parse your own PDFs.
import os, requests
BASE = "https://www.docuclipper.com/api/v1"
KEY = os.environ["DOCUCLIPPER_KEY"] # a dcp_test_... key works here
H = {"Authorization": f"Bearer {KEY}"}
# Pick a sample statement (with a live key, upload your own PDF)
samples = requests.get(f"{BASE}/agent/sandbox/samples", headers=H).json()
doc_id = samples["samples"][0]["documentId"]
# Parse it
job = requests.post(f"{BASE}/agent/jobs", headers=H,
json={"documents": [doc_id]})
job.raise_for_status()
# Read the rows; use them as-is only when the statement ties out
url = f"{BASE}/agent/documents/{doc_id}/transactions"
res = requests.get(url, headers=H).json()
if res["verification"]["status"] == "verified":
for t in res["transactions"]:
print(t["date"], t["description"], t["amount"], t["balance"])
else:
print("Check before using:", res["verification"]["message"])Upload and parse your own PDF in Node
import { readFile } from "node:fs/promises";
const BASE = "https://www.docuclipper.com/api/v1";
const H = {
Authorization: `Bearer ${process.env.DOCUCLIPPER_KEY}`,
"Content-Type": "application/json",
};
const call = (path, init = {}) =>
fetch(BASE + path, { headers: H, ...init }).then((r) => r.json());
const post = (path, body) =>
call(path, { method: "POST", body: JSON.stringify(body) });
// 1. Upload your PDF (live key)
const { url, document } = await post("/agent/documents/upload-url", {
filename: "jan.pdf",
mimetype: "application/pdf",
});
await fetch(url, {
method: "PUT",
headers: { "Content-Type": "application/pdf" },
body: await readFile("jan.pdf"),
});
// 2. Parse it and wait (use a webhook in production)
const { jobId } = await post("/agent/jobs", { documents: [document.id] });
const done = ["Succeeded", "Failed", "OutOfCredits"];
let status;
do {
({ status } = await call(`/agent/jobs/${jobId}?waitSeconds=40`));
} while (!done.includes(status));
// 3. Rows + verdict
const { verification, transactions } = await call(
`/agent/documents/${document.id}/transactions`
);
console.log(verification.status, transactions.length);Step by step with cURL: 60-second quickstart. Skip the waiting in production: webhooks.
What every parsed row contains
| Field | What it holds |
|---|---|
| date | ISO date (YYYY-MM-DD) |
| description | The full transaction description, wrapped lines joined |
| amount | The transaction amount as a number |
| balance | The running balance, where the statement prints one |
| account | Which account on the statement the row belongs to |
| pageNumber, rowNumber | Where the row is in the PDF, for audit trails and review screens |
| rawCells | The row's cells as printed, always present |
| verification | verified, needs_review or unverifiable for the statement, with the periods, pages and difference behind it |
Full schema in the agent API reference and the OpenAPI spec. The plan and what it costs: Bank Statement API.
From test key to production
- 1
Build on sample statements, free
Create a test key in your DocuClipper developer console. It works without limits on our sample bank statements, answers exactly as production will, and uses no pages.
- 2
Go live with 50 free pages
Add a card and your first 50 pages on your own statements are free. Nothing is charged until you choose the plan.
- 3
Scale on one plan
$199 a month with 2,000 pages included, then $0.10 a page. Over 20,000 pages a month, ask for a volume quote.
API plan
$199/month
- 2,000 pages a month included
- $0.10 per page after that
- Free test key on sample statements
- 50 free live pages with a card
Bank statement parser API: FAQ
- What is a bank statement parser?
- A bank statement parser reads a statement file and turns its printed transaction lines into structured rows: date, description, amount and balance. DocuClipper's parser API does this for any bank's PDF, including scans, and also tells you whether each statement period's rows add up to the printed opening and closing balances, so you know when the parse is complete.
- Can I parse bank statement PDFs in Python?
- Yes. Call the REST API with any HTTP client, such as requests: create a job on the PDF, then read GET /agent/documents/<id>/transactions. The example on this page runs as-is against our sample statements with a free test key.
- How is this different from pulling tables out of the PDF myself?
- A table is not a transaction list. Statements wrap descriptions over several lines, split money in and money out across columns, leave the year off dates and put several accounts in one file, and every bank does it differently. The API returns clean transactions for any bank, and the verification verdict tells you when a statement does not add up, so a silent parsing gap cannot reach your users.
- How do I know no transactions were missed?
- Check verification.status. verified means every statement period's rows add up from the printed opening balance to the printed closing balance. needs_review lists the periods that do not, with their pages and the difference. unverifiable means the statement prints no balances to check against, with the reason.
- Does it parse scanned bank statements?
- Yes. Scanned and photographed statements are converted alongside digital PDFs and come back with the same fields, so your code does not need a separate path for them.
- What does the bank statement parser API cost?
- The test key is free on our sample statements and uses no pages. With a card on file you get 50 free live pages, then the API plan is $199 a month with 2,000 pages included and $0.10 per page after that. Above 20,000 pages a month, ask support@docuclipper.com for a volume quote.
- What output formats are available?
- JSON from /transactions and /data, or a CSV download with ?format=csv. The verification verdict is in the JSON and in the X-DocuClipper-Verification response header, so CSV consumers get it too.