From these issuers among many others. There is no supported bank list and no per-issuer template: we read the PDF's own glyph coordinates, so an issuer we have never seen behaves like one we have.
Parse a document
POST the PDF as a raw body or as multipart form data. Up to 20MB.
Send an Idempotency-Key with every request: any unique string, one per document. If a request times out or the connection drops, send the same key again.
Retry as often as you need. While a document keeps failing, every attempt runs fresh and none of them cost anything. Once one succeeds we store that answer, and any later request with the same key gets it back instead of a second parse, marked Idempotent-Replayed: true. So you are never charged twice for one document, however many times you send it. Stored answers are kept for 24 hours, after which the same key returns a 409 rather than quietly parsing and charging again.
Our id for this parse. Quote it in any support question.
issuerobject
The bank as printed, plus its domain for logo lookups.
currencystring
ISO 4217. One per document.
periodobject
The month the document covers: { year, month } with month 1-12.
accountsarray
One entry per account printed on the PDF. Usually one.
accounts[].type"bank" | "credit_card"
bank covers any deposit account: current, checking, savings.
accounts[].primaryboolean
The account the issuer printed first. Exactly one per document.
accounts[].totalsobject
Our sum of the rows: { credits, debits }, both positive.
accounts[].statedTotalsobject | null
What the statement itself prints, exactly as printed.
openingBalancenumber | null
Opening balance, when the issuer prints one.
transactions[].amountnumber
Always positive. direction carries the sign.
transactions[].direction"credit" | "debit"
Resolved from the document, never guessed from the payee.
transactions[].descriptionstring
The payee line as printed. Not normalised or cleaned.
verificationobject
Our own check of the extraction against the document.
Errors
Branch on code, not on the message. We reword messages; we do not renumber codes. Hover any code for the detail.
400empty_body
No PDF arrived. Not charged.
401invalid_key
The key is unknown or has been revoked.
402trial_exhausted
The free allowance is spent and no card is on file.
402monthly_cap_reached
Your monthly document ceiling was hit.
402canceled
Billing for this account was cancelled.
405method_not_allowed
Only POST. The response carries an Allow header.
409idempotency_key_not_replayable
The key succeeded but its stored response is gone.
413file_too_large
Over 20MB.
415not_a_pdf
The body is not a PDF.
422not_a_bank_statement
Readable, but not a statement. Not charged.
429too_many_concurrent_requests
More than 4 documents in flight at once.
500parse_failed
Our error. Not charged, and your cap is not spent.
Before you use the numbers
Two checks worth writing on day one. Skip either and you get a plausible number instead of an error, so nothing tells you it went wrong.
// A card statement has no income on it: its credits are the repayment// and merchant refunds. Only count credits on a deposit account.const income = doc.accounts
.filter((a)=> a.type ==="bank").reduce((sum, a)=> sum + a.totals.credits,0);// Do not present figures we could not reconcile as if they were checked.if(!doc.verification.reconciled){awaitqueueForManualReview(doc.id, doc.verification.issues);}
Frequently asked questions
Documents that come back 200, at $0.49 each. A 422 for a PDF that is not a statement, a 500 on our side, a 402 or a 401 are all free, and none of them count against your monthly ceiling either. Every response says which it was in the X-Billable-Documents header, so you can reconcile against your own count rather than trusting ours. A multi-account PDF is one document. Six months of statements in one PDF is one document. We charge per file you send. Usage is metered to Stripe and invoiced monthly.
The PDF is parsed in memory and never written to disk. No transactions are stored and no account is created for the person whose statement it is. We keep a usage log — timestamp, size, duration, issuer, row count — because invoices are settled from it. If you send an Idempotency-Key the response body is held for 24 hours so the replay can be honoured, then deleted automatically. That body is the only place we hold anybody's transactions at rest, which is why it expires on a timer rather than at our discretion.
Open Banking needs the account holder present, consenting, and banking with a covered institution. Mortgage files, SME lending, tenant screening, immigration and legal disclosure all still run on PDFs handed over by the applicant, often for accounts no aggregator covers. This is for those. Plenty of our customers use both.
There is no supported list, because there is no per-issuer template to maintain. We rebuild the statement's visual rows from the PDF's own glyph coordinates and read those, so an issuer we have never seen works the same way as one we have. UK, US and European statements are all in regular use.
Every response carries a verification object holding our own check of the extraction against the document. Where a statement prints opening and closing balances, the transactions have to account for the movement between them exactly. When that fails we tell you rather than looking confident, because a misread you cannot see is the failure that actually costs you money.
Not today. We read the text layer a bank's own PDF generator produces, which is why the figures are exact rather than best-effort OCR. A scan with no text layer comes back as 422 and is not charged.
The 25 free documents are the sandbox, and they need no payment method. We would rather you evaluate against the real thing than against a mock that behaves differently, so the code you test with is the code that ships.
Parse your first statement today
25 documents free, no payment method. Finish the integration before you decide anything.