DocuClipper logo

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.

python
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

javascript
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

FieldWhat it holds
dateISO date (YYYY-MM-DD)
descriptionThe full transaction description, wrapped lines joined
amountThe transaction amount as a number
balanceThe running balance, where the statement prints one
accountWhich account on the statement the row belongs to
pageNumber, rowNumberWhere the row is in the PDF, for audit trails and review screens
rawCellsThe row's cells as printed, always present
verificationverified, 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. 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. 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. 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
Get a free test key

Volume quote · Pricing

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.