API REFERENCE · V1

Bank Statement Converter API

Convert bank statement PDFs, scans and photos into Excel, CSV, JSON or accounting import files from your own software, with the same parser and AI extraction as the website.

Overview

The BankStatement2PDF API turns bank statements into structured transactions. Send PDFs (searchable or scanned), photos (JPG or PNG), or CSV, OFX, QBO, QFX, QIF and IIF files; get back Excel, CSV, JSON or an accounting import file. Every request is a job: you submit files, the job runs in the background, and you poll it or receive a webhook when it finishes.

Base URL: https://bankstatement2pdf.com/api/v1. All responses are JSON except file downloads.

Authentication

Create a key under Workspace → API. Send it in the X-API-Key header (or as Authorization: Bearer …). Keys start with b2p_live_, are shown once, and can be revoked at any time. Call the API only from your own server; never ship a key to a browser or app.

curl https://bankstatement2pdf.com/api/v1/usage -H "X-API-Key: b2p_live_your_key_here"

Pricing, credits and rate limits

Each page the API reads costs one credit: every PDF page, every image, and one per CSV, OFX, QBO, QFX, QIF or IIF file. Credits are charged when a job is accepted and refunded in full if the job fails. Plans add credits each billing period; unused credits roll over.

PlanCreditsRequests / minuteWebhooks
Free (no subscription)Page packs you buy5—
Starter ($15/month)500 pages / month10—
Professional ($30/month)1,200 pages / month30✓
Business ($50/month)5,000 pages / month50✓

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds). Over the limit you get 429 with a Retry-After header.

POST /convert — submit a job

Multipart form upload. Returns 202 Accepted with the job; the work happens in the background.

FieldRequiredDescription
fileyesOne or more files (repeat the field). Up to 20 files, 50 MB each, 200 MB per request. PDF, JPG, PNG, CSV, OFX, QBO, QFX, QIF, IIF.
outputnoxlsx (default), csv, json, qbo, qfx, ofx, qif, iif, tally or pdf.
webhook_urlnoHTTPS URL notified when the job completes or fails (Professional and Business).
date_ordernomdy or dmy for CSV dates that are not YYYY-MM-DD.
account, currency, bank_id, account_typefor OFX/QBO/QFXAccount ID, ISO currency, routing/bank ID, and CHECKING, SAVINGS or CREDITCARD.
institution, closing_balance, balance_datefor OFX/QBO/QFXNumeric institution ID (QBO/QFX), the actual ledger balance and its date.
bank_ledger, counter_ledgerfor IIF/TallyLedger names for the bank and the counterparty side.
curl https://bankstatement2pdf.com/api/v1/convert \
  -H "X-API-Key: $B2P_API_KEY" \
  -F "[email protected]" \
  -F "output=json"
HTTP/1.1 202 Accepted
Location: /api/v1/jobs/7c1e…

{
  "id": "7c1e9a52-0b6f-4c55-9d3e-2f4a8c1b6e10",
  "status": "processing",
  "output": "json",
  "files": 1,
  "pages_charged": 3,
  "ai_pages": 0,
  "transaction_count": null,
  "download_url": null,
  …
}

Searchable PDFs in a known layout are parsed directly. Unknown layouts, scanned PDFs and photos are read with AI extraction; the result reports how many pages used it in ai_pages, and warnings says whether running balances and totals reconcile.

GET /jobs/{id} — job status

status is processing, completed or failed. Most jobs finish in seconds; scans take longer. Poll every 2–5 seconds or use a webhook.

{
  "id": "7c1e9a52-0b6f-4c55-9d3e-2f4a8c1b6e10",
  "status": "completed",
  "output": "json",
  "created_at": "2026-10-12T09:14:03.120Z",
  "completed_at": "2026-10-12T09:14:05.871Z",
  "expires_at": "2026-10-13T09:14:03.120Z",
  "files": 1,
  "pages_charged": 3,
  "ai_pages": 3,
  "transaction_count": 42,
  "summary": [{ "account": "", "currency": "USD", "count": 42, "moneyIn": "3240.00", "moneyOut": "2911.47", "net": "328.53" }],
  "warnings": ["march-statement.pdf: Totals reconcile: opening 1846.20 + transactions = closing 2174.73."],
  "error": null,
  "download_url": "https://bankstatement2pdf.com/api/v1/jobs/7c1e…/download",
  "also_available_as": ["csv", "xlsx"],
  "webhook_status": null
}

A failed job has error.code (conversion_failed, interrupted or internal_error) and error.message; its credits are already refunded.

GET /jobs/{id}/download — get the file

Returns the file in the job's output format. Add ?format=json, csv or xlsx to get the same transactions in another format. Files are kept for 24 hours, then this returns 410.

curl -L -o march.json "https://bankstatement2pdf.com/api/v1/jobs/7c1e…/download" -H "X-API-Key: $B2P_API_KEY"

JSON output lists each transaction with date (YYYY-MM-DD), description, amount and balance as decimal strings (negative = money out), plus currency, account, reference and sourceFile.

GET /jobs — recent jobs

Your 20 most recent API jobs, newest first (?limit= up to 100), as {"data": [job, …]}.

GET /usage — credits and limits

{
  "plan": "professional",
  "credits_remaining": 1088,
  "pages_used_last_30_days": 112,
  "rate_limit_per_minute": 30,
  "webhooks": true
}

Webhooks

Pass webhook_url with a job to receive a POST when it completes (job.completed) or fails (job.failed). The body holds the same job object as GET /jobs/{id}. Respond with any 2xx within 10 seconds; failed deliveries are retried after 30 seconds and 5 minutes. Only public HTTPS endpoints are accepted.

POST /your/endpoint
B2P-Signature: t=1791797645,v1=5f1c…
Content-Type: application/json

{ "event": "job.completed", "created_at": "2026-10-12T09:14:05.900Z", "data": { "id": "7c1e…", "status": "completed", … } }

Verify every delivery with your webhook signing secret (Account → API access): compute HMAC-SHA256 of t + "." + raw body and compare it with v1; reject timestamps older than five minutes.

import crypto from "node:crypto";

export function verifyB2PWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 || ""));
}

Errors

Errors return {"error": {"code": "…", "message": "…"}}.

StatusCodeMeaning
400missing_file, invalid_output, invalid_webhook_url, invalid_uploadFix the request.
401invalid_api_keyMissing, malformed or revoked key.
402insufficient_creditsNot enough credits; includes pages_required and balance.
403webhooks_not_in_planWebhooks need Professional or Business.
404not_foundNo such job, or it belongs to another account.
410expiredThe 24-hour download window has passed.
413payload_too_largeA file over 50 MB or a request over 200 MB.
415unsupported_file_typeSend PDF, JPG, PNG, CSV, OFX, QBO, QFX, QIF or IIF.
422unreadable_fileA damaged or password-protected PDF.
429rate_limitedSlow down; see Retry-After.
500internal_errorTry again; nothing was charged for a request that failed here.

Data handling

Uploaded files are processed and discarded; exports and extracted rows are deleted after 24 hours, and job records after 90 days. Files read with AI extraction are sent to Kie.ai (DeepSeek V4.1 Flash) for that step. See the privacy policy.