Building a bank statement extraction API into your product
If you are embedding document extraction into an ERP or accounting product, these are the decisions that will bite you in month three rather than week one.
Embedding document extraction into a product you already sell is a different problem from converting a file yourself. You inherit someone else's failure modes, and your customers will attribute them to you.
Asynchronous, not synchronous
Extraction takes between a few seconds and a couple of minutes. Any API that promises to do it inside one HTTP request is either very fast infrastructure or about to time out on exactly the large documents that matter most.
The right shape is: submit and receive a job handle, then poll or receive a webhook. Honour the Retry-After header rather than polling in a tight loop — it exists so the service can push back when it is busy instead of falling over.
submit = POST /v1/documents -> 202 { id, polling_url }
poll = GET /v1/documents/{id} -> 200 { status: processing } (Retry-After: 3)
-> 200 { status: succeeded, ... }
Idempotency is not optional
A ninety-second job will be retried through client timeouts, proxy timeouts and deployment restarts. Without an idempotency key, each retry creates a new job and a new charge. This is the single most common way an integration quietly costs double.
Send a UUID as Idempotency-Key on every submit. A retry with the same key and the same document should return the original job, not a new one.
Handle confidence, do not ignore it
A good extractor tells you which rows it was unsure about. A bad integration ignores that and posts everything straight into the ledger.
Prefer coarse confidence buckets over floating point scores — nobody can write a defensible business rule against 0.83, but everyone can write one against four named levels. The pattern that works:
- Reconciled and no flagged rows: post automatically.
- Reconciled with flagged rows: post, but surface the flagged rows for review.
- Not reconciled: never post automatically. Route to a human with the discrepancy shown.
Ask about data residency before you build
If your customers are in regulated sectors, where the document is processed is a contractual question, not a technical preference. Establish three things early: which region processes and stores the document, whether any third-party AI or OCR service sees it, and how long it is retained.
Several well-known providers store EU customer data in US regions, and some contradict themselves between their marketing site and their security policy. Read the security page, not the homepage.
A working integration
import requests, uuid, time
submit = requests.post(
f"{BASE}/documents",
headers={"Authorization": f"Bearer {KEY}", "Idempotency-Key": str(uuid.uuid4())},
files={"file": open("statement.pdf", "rb")},
).json()
while True:
r = requests.get(submit["polling_url"], headers={"Authorization": f"Bearer {KEY}"})
doc = r.json()
if doc["status"] in ("succeeded", "failed"):
break
time.sleep(int(r.headers.get("Retry-After", 3)))
if doc["reconciliation"]["statement_balanced"]:
post_to_ledger(doc["transactions"])
else:
queue_for_review(doc)
Questions worth asking any vendor
- Is the API async, and do you publish a Retry-After?
- Do you support idempotency keys, and for how long?
- Do you return a reconciliation result, or only balances?
- What is the retention window, and is there a delete endpoint?
- Which region processes the document, and which sub-processors see it?
- Is there an on-premise option, and what does it cost?
Frequently asked questions
How long does an extraction take?
A machine-readable statement is usually a few seconds. A scanned document that needs OCR takes longer, roughly proportional to page count. Poll or use a webhook rather than blocking a request thread.
What happens if extraction fails?
You get a stable error code — for example no_transactions_detected or no_text_found — rather than a prose message, and the document does not consume your quota. Branch on the code, never on the text.
Do you have SDKs?
The full OpenAPI 3.1 specification is published, so you can generate a client in any language. Copy-paste examples for PHP, Python, JavaScript and C# are in the API documentation.
How are webhooks secured?
Every delivery carries an HMAC-SHA256 signature over the timestamp and body. Verify it with a constant-time comparison and reject anything older than a few minutes to prevent replay.
Can we run it on our own servers?
Yes. The pipeline has no mandatory external dependencies, so it can run inside your infrastructure under licence, including fully air-gapped.
Try it on your own statements
Free tools with no account, or a workspace with an API and free documents to evaluate it.