# Bank Statement PDF to CSV and JSON, Balance-Checked (`nairsarc/bank-statement-pdf-to-csv`) Actor

Convert US bank and credit card statement PDFs to transaction rows, including scanned statements. Every statement is checked against its own opening and closing balances. No AI, and billed only for statements that produce transactions.

- **URL**: https://apify.com/nairsarc/bank-statement-pdf-to-csv.md
- **Developed by:** [Akshay Nair](https://apify.com/nairsarc) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $30.00 / 1,000 statement pages

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bank Statement PDF to CSV and JSON, Balance-Checked

Turn US bank and credit card statement PDFs into clean transaction rows: date, description, signed amount and running balance. Scanned statements work too.

Every statement is checked against its own printed balances: **opening balance + every row = closing balance**. You get told whether it adds up, so you know which conversions to trust without reading them line by line.

### Why use it

- **Checked, not guessed.** Each statement comes back `balanced`, `off` (with the exact difference) or `unchecked`. There's no LLM involved, so it can't invent a transaction.
- **Reads scans.** Image-only PDFs go through built-in OCR at no extra charge.
- **Handles real statement layouts.** Separate debit and credit columns, tables grouped by type with unsigned amounts, checks listed several to a line, descriptions that wrap, statements with no year printed, and several accounts in one PDF.
- **Credit cards too.** Charges come out as money out, and the check runs against the balance owed.
- **You only pay for results.** Files that are unreadable, password-protected or contain no transactions aren't billed.

### How to use it

1. Point the actor at your PDFs, either way:
   - **Links to statement PDFs:** paste direct links (signed S3, Drive or Dropbox links work).
   - **Stores with PDFs:** choose key-value stores in your Apify account that hold the PDFs. Every PDF in a chosen store is read, and the actor gets read access to those stores only.
2. Click **Start**.
3. Get all transactions from the **Output** tab, as CSV, JSON, Excel or through the API.

Common uses: bookkeeping catch-up, loan and mortgage document processing, tax preparation, and feeding statements into accounting tools or AI agents. Banks usually export CSV for only recent activity, so older history often exists only as PDFs.

### Input

| Field | What it does |
| --- | --- |
| `statements` | Direct links to the PDFs. |
| `statementStores` | Key-value stores in your account holding PDFs (records ending in `.pdf`). |
| `statementYear` | Year to use for statements that print no year anywhere. Optional; defaults to the current year. Statements that print a year ignore it. |

Give at least one link or store.

```json
{
  "statements": ["https://statementtospreadsheet.com/sample-statement.pdf"]
}
```

### Output

**One dataset row per transaction**, across all statements. Money out is negative.

```json
{
  "statement": "sample-statement.pdf",
  "date": "2025-12-28",
  "description": "COFFEE CORNER #12",
  "amount": -45.1,
  "balance": 954.9,
  "statementStatus": "balanced"
}
```

**A `SUMMARY` record** in the run's key-value store, with one report per statement:

```json
{
  "statement": "sample-statement.pdf",
  "status": "balanced",
  "pages": 1,
  "transactions": 4,
  "scanned": false,
  "yearAssumed": false,
  "card": false,
  "accounts": 1,
  "openingBalance": 1000,
  "closingBalance": 1764.75,
  "computedBalance": 1764.75,
  "difference": 0,
  "csvKey": "1-sample-statement.csv",
  "note": "Opening balance plus every row equals the closing balance."
}
```

**A CSV for each statement**, in the same store under its `csvKey`, ready for Excel or Google Sheets.

#### What each status means

| Status | Meaning | Billed? |
| --- | --- | --- |
| `balanced` | Opening balance plus every row equals the closing balance. | Yes |
| `off` | The rows don't add up, usually because OCR misread a figure on a scan. `difference` says by how much. Check before relying on it. | Yes |
| `unchecked` | Rows were found, but not the opening and closing balances. | Yes |
| `no-transactions` | Nothing recognizable as transactions. | No |
| `password-protected` | Remove the password and try again. | No |
| `unreadable` | Not a readable PDF. | No |
| `download-failed` | The file couldn't be fetched. `note` says why. | No |
| `over-budget` | Your run's spending limit doesn't cover this statement, so it was skipped. | No |

### Pricing

Pay per page, for statements that produce transactions, including their legal and marketing pages. There's no subscription. Set a maximum cost per run in Apify and the actor stops before going over it.

### What it works with

US checking, savings and credit card statements in US dollars. It has been tested on statements in the layouts of Chase, Bank of America, Wells Fargo, Capital One, U.S. Bank, PNC, Navy Federal, USAA, Ally, Huntington, KeyBank, Chime and SoFi.

**Limits to know about:**

- Non-US statements aren't supported yet: day-first dates, other currencies and European number formats won't read correctly.
- OCR can misread figures on faint or fax-quality scans. The balance check catches this, and the statement comes back as `off` instead of looking fine.
- Banks change their layouts. If yours comes back as `no-transactions`, please open an issue on the **Issues** tab.

### Privacy

Your files are processed on Apify's servers during the run. The actor reads only the links and stores you give it and sends your data nowhere else: no AI services and no third-party APIs. It doesn't write statement contents or file names to the run log. Results are saved in your own Apify storage, under your storage retention settings.

Want nothing uploaded at all? [statementtospreadsheet.com](https://statementtospreadsheet.com) does the same conversion entirely in your browser.

### Disclaimer

Output is provided as-is. It isn't accounting, tax or financial advice, so check important figures against the original statement. This actor isn't affiliated with any bank named above.

# Actor input Schema

## `statements` (type: `array`):

Direct links to PDFs, one per line (signed S3, Drive or Dropbox links work). Text PDFs and scanned statements are both read.

## `statementStores` (type: `array`):

Key-value stores in your Apify account that hold statement PDFs. Every PDF in a chosen store is read. The actor gets read access to these stores only.

## `statementYear` (type: `integer`):

Some statements print dates like "10/02" and no year anywhere. Those use this year. Leave empty for the current year. Statements that do print a year ignore this.

## Actor input object example

```json
{
  "statements": [
    "https://statementtospreadsheet.com/sample-statement.pdf"
  ]
}
```

# Actor output Schema

## `transactions` (type: `string`):

One row per transaction, across all statements.

## `summary` (type: `string`):

Status, page count, balances and the CSV record's key, for each statement.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "statements": [
        "https://statementtospreadsheet.com/sample-statement.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nairsarc/bank-statement-pdf-to-csv").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "statements": ["https://statementtospreadsheet.com/sample-statement.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("nairsarc/bank-statement-pdf-to-csv").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "statements": [
    "https://statementtospreadsheet.com/sample-statement.pdf"
  ]
}' |
apify call nairsarc/bank-statement-pdf-to-csv --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nairsarc/bank-statement-pdf-to-csv"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/3uWTh6R8vgUexYcaO/builds/YZUr3E1m4RuACF20o/openapi.json
