Bank statement extraction API: a developer integration guide
Build a production-minded bank statement extraction flow with bearer authentication, preflight validation, asynchronous jobs, normalized JSON, and CSV or XLSX exports.
Prepared by
bankstatement.ai engineering
A bank statement extraction API should do more than turn a PDF into text. It should give your product a controlled path from a sensitive file to transaction data your application can validate, review, and export.
bankstatement.ai uses an asynchronous REST workflow. Your backend authenticates with a bearer token, validates an upload, starts a job, polls its status, then retrieves normalized results or spreadsheet exports. Supported inputs are PDF, CSV, and XLSX.
Short version: keep the token on your server, validate before processing, treat job status as a state machine, and copy completed results into your own system before the seven-day result window closes.
The request lifecycle
The API lifecycle has five boundaries:
| Stage | Endpoint | Your responsibility |
|---|---|---|
| Validate | POST /api/v1/uploads/preflight |
Reject unsupported or oversized files before starting work. |
| Create | POST /api/v1/jobs |
Upload one statement and choose a processing mode. |
| Monitor | GET /api/v1/jobs/:id |
Poll until the job is complete or failed. |
| Retrieve | GET /api/v1/results/:id |
Map normalized transaction data into your application. |
| Export | GET /api/v1/results/:id/exports/:kind |
Download a CSV or XLSX artifact when a spreadsheet is the destination. |
The base URL is:
https://api.bankstatement.ai/api/v1
Successful job creation returns HTTP 202. Processing continues outside the request, so your application does not need to hold an upload connection open while OCR and transaction extraction run.
When this API fits
Use the API when statement processing belongs inside your product rather than inside a separate operator tool. Common fits include:
- an accounting product importing bank transactions;
- a bookkeeping workflow normalizing client statements;
- a finance product accepting statement documents from users;
- a reconciliation-preparation flow that needs structured rows;
- an internal operations system processing statement batches.
The API supports two processing modes. Use convert when you need normalized transaction rows and exports. Use companies when you need merchant grouping for an invoice-collection worklist.
Neither mode should silently become an accounting decision engine. Your application still owns account mapping, reconciliation rules, exception handling, and downstream approval.
Keep bearer authentication server-side
Create and manage your API token from the API account page. Send it in the Authorization header:
Authorization: Bearer bsai_live_YOUR_TOKEN
Do not ship the token in browser JavaScript, a mobile application, or a public repository. Put the API call behind your own backend. That backend can authenticate your user, enforce product permissions, submit the statement, and return only the job data that user may access.
A minimal server-side request looks like this:
const response = await fetch("https://api.bankstatement.ai/api/v1/jobs", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BANKSTATEMENT_API_TOKEN}`,
},
body: formData,
});
Treat token rotation as a normal operational event. Read tokens from a secret store, avoid logging the header, and make replacement possible without a code deployment.
Validate the upload before processing
Preflight catches avoidable failures before your product reserves pages or creates a processing job. Send the file metadata and chosen mode to POST /uploads/preflight.
curl -X POST "https://api.bankstatement.ai/api/v1/uploads/preflight" \
-H "Authorization: Bearer bsai_live_YOUR_TOKEN" \
-F "fileName=sample-statement.pdf" \
-F "fileSize=184320" \
-F "mimeType=application/pdf" \
-F "mode=convert" \
-F "pageCount=3"
Current upload limits are:
- PDF, CSV, or XLSX input;
- 25 MB maximum per file;
- 100 pages maximum per PDF;
- 30 files maximum in one batch.
Use a synthetic statement during development. It protects real financial data, makes regression tests repeatable, and lets your team check failure handling without placing a customer's document in logs or fixtures.
Client-side validation improves feedback, but server-side preflight remains the authority. File extensions, browser MIME types, and user-supplied page counts are not trustworthy enough to be the only validation layer.
Upload one statement and start a job
Submit the statement as multipart/form-data. This cURL request starts convert mode:
curl -X POST "https://api.bankstatement.ai/api/v1/jobs" \
-H "Authorization: Bearer bsai_live_YOUR_TOKEN" \
-F "mode=convert" \
-F "statement=@./sample-statement.pdf"
Store the returned job ID with your own import record. A useful internal record includes:
- your user or workspace ID;
- the bankstatement.ai job ID;
- original file name, without file contents;
- selected processing mode;
- your current import state;
- creation and last-checked timestamps;
- terminal failure reason safe for operators to see.
Avoid recording tokens, uploaded file contents, or raw transaction descriptions in routine application logs.
Poll jobs as a state machine
Poll GET /jobs/:id until status is complete or failed.
const baseUrl = "https://api.bankstatement.ai/api/v1";
const headers = {
Authorization: `Bearer ${process.env.BANKSTATEMENT_API_TOKEN}`,
};
async function waitForStatementJob(jobId) {
let delayMs = 1_500;
for (let attempt = 0; attempt < 40; attempt += 1) {
const response = await fetch(`${baseUrl}/jobs/${jobId}`, { headers });
if (response.status === 429) {
const limited = await response.json();
await new Promise((resolve) =>
setTimeout(resolve, limited.retryAfterSeconds * 1_000),
);
continue;
}
if (!response.ok) throw new Error(`Job check failed: ${response.status}`);
const job = await response.json();
if (job.status === "complete") return job;
if (job.status === "failed") throw new Error("Statement processing failed");
await new Promise((resolve) => setTimeout(resolve, delayMs));
delayMs = Math.min(Math.round(delayMs * 1.5), 10_000);
}
throw new Error("Statement processing is still pending");
}
This is more than a loop. Your product needs a visible pending state, a bounded timeout, a retry strategy, and a way to resume polling after a process restart. Persist the job ID before polling. A browser refresh or worker restart should not create another paid job for the same upload.
When the API returns 429, respect retryAfterSeconds. When it returns 402, the account needs more pages. Surface those cases separately from malformed files or provider failures because each needs a different user action.
Map completed results deliberately
After completion, request GET /results/:id. The result contains normalized transaction data for your application to inspect and map.
Keep three layers separate:
- Extraction: values returned from the statement.
- Normalization: your canonical date, description, and amount representation.
- Accounting interpretation: ledger account, category, reconciliation status, or review decision.
This boundary prevents an OCR result from being treated as an approved accounting entry. Preserve enough source context to review exceptions, but do not invent account metadata or balances when the document does not provide them.
Use the verification summary as a review signal. It can help route a result for operator inspection, but it is not a field-level confidence score or a guarantee that every accounting interpretation is correct.
Download CSV or XLSX exports
When the destination is a spreadsheet, download an export instead of rebuilding it from JSON:
import requests
headers = {"Authorization": "Bearer bsai_live_YOUR_TOKEN"}
response = requests.get(
"https://api.bankstatement.ai/api/v1/results/result_id/exports/xlsx",
headers=headers,
timeout=30,
)
response.raise_for_status()
with open("statement.xlsx", "wb") as output:
output.write(response.content)
Use csv or xlsx as the export kind. Copy the artifact into your approved storage only when your workflow requires it, then apply your own access controls and retention policy.
Process batches without losing control
POST /jobs/batch accepts up to 30 files under the statements multipart field. Batch processing reduces request overhead, but it should not erase per-file state.
Track each returned job independently. One failed statement should remain diagnosable without forcing an operator to repeat every successful file. Calculate available pages before submission, validate all files, then enqueue the batch once your product can represent every returned job.
For large workloads, feed batches from your own queue. This gives you control over concurrency, retries, customer-level limits, and backpressure.
Design for retention and observability
Results and exports remain available for seven days. API call metadata appears in the account for 90 days. Build retrieval around those windows:
- fetch completed results promptly;
- copy only data your product must retain;
- avoid waiting until day seven for an export;
- record job and result IDs for support;
- keep financial contents out of telemetry;
- delete local temporary uploads after the job is safely created.
API metadata is useful for diagnosing status codes and request timing. It is not a replacement for your own product-level audit trail. Your audit trail should answer who initiated an import, which internal record it affected, and what action an operator took after review.
Production integration checklist
Before moving from a proof of concept to production, verify:
- API tokens stay in server-side secrets;
- supported formats and limits are enforced before upload;
- duplicate user actions cannot create duplicate jobs;
- polling uses backoff and respects
retryAfterSeconds; - job IDs survive restarts and browser refreshes;
402,429, validation, and processing failures have distinct UI states;- transaction mapping handles missing values explicitly;
- completed results are retrieved inside the seven-day window;
- logs exclude tokens, file contents, and transaction descriptions;
- tests use synthetic statements across PDF, CSV, and XLSX paths.
The fastest useful proof of concept is one synthetic statement moving through the complete lifecycle: preflight, job creation, polling, result retrieval, and export. Review the live endpoint contract in the API documentation, choose a plan based on page volume on the pricing page, then create your token from the API account page.
Next action
Test with a synthetic statement.
Create an account, get an API token, then validate the full request lifecycle.