REST API · PDF → JSON, CSV, XLSX

Bank Statement PDF Extraction API

A bank statement API that extracts every transaction from a customer-supplied PDF — not an Open Banking feed. Get JSON inline, or XLSX and CSV as files.

POST /api/v1/developer/jobs → statement.json
Create job
curl -X POST https://api.mainbook.ai/api/v1/developer/jobs \
  -H "Authorization: Bearer mb_live_…" \
  -H "Content-Type: application/json" \
  -d '{"filename":"march.pdf","file_format":"pdf","size_bytes":184320,"page_count":4}'
201 Created
{  "job_id": "8f14e45f-ceea-467a-9b2e-3f7c1a5d20b1",  "credits_reserved": 4,  "upload": {    "url": "https://mainbook.nyc3.digitaloceanspaces.com/…",    "method": "PUT",    "headers": { "Content-Type": "application/pdf" },    "expires_at": "2026-08-14T09:15:00Z"  }}
The boundary

This is a document API, not Open Banking

The source is a customer-supplied statement, not a connected bank account.

statement.pdf → structured data
Document APIPDF supplied by customer
statement.pdf
DateDescriptionAmount
2026-03-03ACH CREDIT+$2,400
Open BankingOutside this API
Connect your bank
Authorize live access
No bank loginNo authorization flowNo consent screenNo live feedNo balance polling

The same endpoint works as a bank statement to JSON API; scanned PDFs run through OCR — a bank statement OCR API in practice. For live account access, use an Open Banking provider.

One explicit state machine

How a job moves from PDF to result

Five ordered calls carry the file through eight public states; polling is the supported mechanism.

mainbook API · job lifecycle

The five calls

  1. 01request
    POST /api/v1/developer/jobs

    Required: filename · file_format · size_bytes · page_count. Reserves page credits and returns upload instructions.

  2. 02request
    PUT to the presigned upload.url

    PUT within about 15 minutes. Return every upload.headers value verbatim; the headers are signed.

  3. 03request
    POST /api/v1/developer/jobs/{job_id}/start

    Start after the PUT. Safe to retry after a timeout; it will not enqueue the same job twice.

  4. 04request
    GET /api/v1/developer/jobs/{job_id}

    Poll every 3–5 seconds. Stop when the job enters any of five terminal states.

  5. 05result
    GET /api/v1/developer/jobs/{job_id}/result?type=json

    Required query: type, not format. Accepts json · xlsx · csv.

Keep polling

3 in-flight states · every 3–5s
  • awaiting_upload

    The job is waiting for the presigned PDF upload.

  • queued

    The uploaded job is waiting for processing.

  • processing

    The statement is being processed.

Stop polling

5 terminal states
  • succeeded

    Processing finished without warnings.

  • succeeded_with_warnings

    Processing finished and the result carries warnings to review.

  • insufficient_credits

    The job stopped because the page-credit balance was insufficient.

  • failed

    The job stopped with an error.

  • expired

    The upload window or reserved-credit hold closed before the job started.

The result contract

What the result actually contains

Statement facts stay separate from transaction rows, whether the consumer reads JSON or opens Excel.

GET /jobs/{job_id}/result?type=json
application/json
{  "document": {    "bank_name": "Chase",    "account_number_masked": "••••4821",    "account_type": "checking",    "currency": "USD",    "period_start": "2026-03-01",    "period_end": "2026-03-31",    "starting_balance_cents": 412750,    "ending_balance_cents": 388105,    "net_credits_cents": 540000,    "net_debits_cents": 564645,    "transactions_count": 63  },  "transactions": [    {      "row": 1,      "date": "2026-03-03",      "description": "ACH CREDIT · PAYROLL",      "amount_cents": 240000,      "transaction_type": "credit",      "balance_after_cents": 652750,      "currency": "USD",      "validation_status": "ok",      "warning_flags": []    }  ],  "has_warnings": false}
march.xlsx · transactions

Chase

bank

USD

currency

63

transactions

ABCDE
DateDescriptionAmountBalanceStatus
2026-03-03ACH CREDIT · PAYROLL+$2,400.00$6,527.50ok
2026-03-04ACH DEBIT · RENT−$1,525.00$5,002.50ok
2026-03-05CARD · WHOLE FOODS−$86.32$4,916.18ok
2026-03-05CARD · STARBUCKS−$18.75$4,897.43ok
2026-03-06CARD · AWS−$780.40$4,117.03ok
2026-03-07CARD · OFFICE DEPOT−$214.67$3,902.36ok
2026-03-09ZELLE PAYMENT · J. MILLER−$250.00$3,652.36ok
2026-03-10CARD · T-MOBILE−$65.49$3,586.87ok
2026-03-11ACH CREDIT · CLIENT PAYMENT+$450.00$4,036.87ok
2026-03-12CARD · DROPBOX−$39.95$3,996.92ok
2026-03-13CARD · UBER−$27.40$3,969.52ok
2026-03-16ACH DEBIT · VENDOR PAYMENT−$1,820.00$2,149.52ok
2026-03-17ACH CREDIT · PAYROLL+$2,400.00$4,549.52ok
2026-03-18CARD · SHELL OIL−$94.27$4,455.25ok
2026-03-19CARD · ADOBE−$59.99$4,395.26ok
transactionsdocument15 of 63 rows shown
  • Money in integer cents
  • ISO 8601 dates
  • Sorted keys
  • UTF-8

The complete field list, nullable card fields, running-balance rules, and live types are in the OpenAPI schema.

Validation, not guesswork

Every job says whether the maths held

Read the three validation fields together; each answers a different question about the result.

validation · statement arithmetic

Totals match

Opening + credits − debits = closing

0 mismatched rows
reconcilable
true
Both sides plus opening and closing figures exist. False means nothing to check; null means no verdict.
passed
true
No warnings. If reconcilable is false, this does not confirm the arithmetic.
mismatched_rows
0
Rows whose own arithmetic needs human review.
"validation": { "reconcilable": true, "passed": true, "mismatched_rows": 0 }
Before production

What to know before production

Keep the secret server-side, build to the published limits, and branch on stable reason codes.

production checklist · live v1 contract
Authorization: Bearer mb_live_…server only · full secret shown once

Keep it on your server

Never expose the account key in browser JavaScript.

Browser requests are refused

Third-party origins receive an opaque network error.

Idempotency-Key · optional

Same key plus a new body conflicts; v1 publishes no TTL.

Production limits

File boundary

500 pages
maximum per file
50 MB
maximum per file

Request pace

6 jobs
in flight per API key
5 per second
job creates or starts
2 per second
status polls
2 per 10 seconds
result downloads
30 per minute
failed auth attempts per IP

Account and storage

20
active keys per account
90 days
document and result retention
1 credit = 1 PDF page20 credits with a new accountBuy in the web app · from $25 for 138 pages →
PDF inJSONCSVXLSX
reasonHTTPWhat to do
invalid_key401Send a valid Bearer key; 401 uses {"detail":"invalid_key"} instead of reason.
insufficient_credits402Top up page credits; the body carries available and requested.
job_not_terminal409Keep polling the job and request the result only after a terminal state.
rate_limited429Wait for the Retry-After delay before retrying.
engine_paused503Processing is behind the global backstop; do not treat the message as a stable code.

Except for the 401 body, branch on reason, not on the human-readable message. Rate limits return 429 with Retry-After. The live OpenAPI schema is the complete, current error contract.

API calls require accepted API Terms. Read how files are handled in Security; the remaining data terms are in the Privacy Policy and Data Processing Agreement.

The common questions

Frequently asked questions

Is this an Open Banking API?
No. It turns a PDF the account holder already has into structured data. It does not log in to a bank, show a consent screen, open a live transaction feed or poll a bank balance.
Can it read a scanned statement or a photo?
The public API accepts a PDF, and a scanned PDF goes through OCR in the same pipeline as a digital one. If the source is a photo, put the image in a PDF before sending it; file_format accepts pdf.
What formats can I get back?
JSON inline, or XLSX and CSV as file attachments. The required result query parameter is named type and accepts json, xlsx or csv.
How do I know the numbers are right?
A finished job includes validation with reconcilable, passed and mismatched_rows. Read reconcilable and passed together: passed while reconcilable is false means nothing was off, not that the arithmetic was confirmed.
How long does a conversion take?
Processing is asynchronous. Poll every 3–5 seconds until the job reaches a terminal state; large statements take longer, and there is no published SLA.
Are there webhooks?
No. Polling the job status is the supported mechanism today.
How much does it cost?
Credits are counted in PDF pages and use the same wallet as the web app. A new account receives 20 page credits on signup. Packages start at $25 for 138 pages.

Build against the live contract

Create the key in your account, keep it on the server, and use the published schema as the source of truth.