DocFig Convert a statement

Bank statement API: PDFs to JSON, Excel and CSV

Send a bank or credit card statement, get every transaction back as clean JSON, with each row checked against the running balance and the printed totals. The same converter as the website, from code. A free key gives 100 pages a month.

Get a free API key

Enter an email address. A 6-digit code arrives in a minute; enter it and the key is shown once. No password and no card.

The address is used for the key only, never shared. Already have keys? The same form opens the page that lists and revokes them. See Privacy.

Quick start

Every request sends the key as Authorization: Bearer <key> (or X-API-Key: <key>). Upload a file, poll until status is done, then read the JSON or download the spreadsheet.

export DOCFIG_API_KEY=dfk_...

# 1. Upload. Returns an id at once (HTTP 202).
curl -H "Authorization: Bearer $DOCFIG_API_KEY" \
     -F file=@statement.pdf https://docfig.com/v1/statements

# 2. Poll every 2 seconds until "status": "done".
curl -H "Authorization: Bearer $DOCFIG_API_KEY" \
     https://docfig.com/v1/statements/st_XXXX

# 3. The same Excel or CSV file the website makes.
curl -H "Authorization: Bearer $DOCFIG_API_KEY" -o statement.xlsx \
     https://docfig.com/v1/statements/st_XXXX.xlsx

Password-protected PDF: add -F password=XXXX. The password opens the file in memory once and is never stored or logged.

Python

pip install docfig

import docfig

client = docfig.Client()                 # reads DOCFIG_API_KEY
st = client.convert("statement.pdf")     # uploads, waits, returns the result
print(st.bank, st.checks["status"], len(st.transactions))
st.download("statement.xlsx")            # or "statement.csv"

combined = client.combine(["jan.pdf", "feb.pdf", "mar.pdf"])
for p in combined.problems:
    print(p["kind"], p["text"])          # gaps, carry-over, breaks

Errors are typed: docfig.AuthenticationError, PasswordRequiredError, WrongPasswordError, LimitReachedError (with upgrade_url), RateLimitError, InvalidFileError, ConversionFailedError. Rate limits and busy moments are retried for you.

Node.js

npm install docfig-api

import { DocFig } from "docfig-api";

const client = new DocFig();                         // reads DOCFIG_API_KEY
const st = await client.convert("statement.pdf");    // path, Buffer or Blob
console.log(st.bank, st.checks.status, st.transactions.length);
await st.download("statement.xlsx");

const combined = await client.combine(["jan.pdf", "feb.pdf"]);
console.log(combined.counts);

Node 18 or later, no dependencies (native fetch). ESM and CommonJS, with TypeScript types.

MCP server for AI assistants

DocFig also runs as an MCP server, so Claude, ChatGPT, Cursor, VS Code and other AI apps can read statements for you. Four tools:

The hosted server is https://mcp.docfig.com/mcp (Streamable HTTP). Send the key as Authorization: Bearer dfk_.... Files go by https link or as base64; the local server below can also read files on the computer.

Claude Desktop

Settings, Developer, Edit Config, then add to claude_desktop_config.json and restart Claude:

{
  "mcpServers": {
    "docfig": {
      "command": "npx",
      "args": ["-y", "docfig-api", "mcp"],
      "env": { "DOCFIG_API_KEY": "dfk_..." }
    }
  }
}

With Python instead of Node: "command": "uvx", "args": ["docfig", "mcp"]. On Claude.ai, add a custom connector with the URL https://mcp.docfig.com/mcp/dfk_....

Cursor

Add to ~/.cursor/mcp.json (or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "docfig": {
      "url": "https://mcp.docfig.com/mcp",
      "headers": {
        "Authorization": "Bearer dfk_..."
      }
    }
  }
}

VS Code

Add to .vscode/mcp.json. VS Code asks for the key once and stores it securely:

{
  "servers": {
    "docfig": {
      "type": "http",
      "url": "https://mcp.docfig.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:docfig-key}"
      }
    }
  },
  "inputs": [
    {
      "id": "docfig-key",
      "type": "promptString",
      "description": "DocFig API key",
      "password": true
    }
  ]
}

ChatGPT

In ChatGPT settings, turn on developer mode for connectors, then create a connector with the URL https://mcp.docfig.com/mcp/dfk_... and no authentication. The key is part of that URL, so keep it private; if it is ever shared, revoke the key on the keys page and make a new one.

Local server

# Node 18 or later
npx -y docfig-api mcp

# Python
uvx docfig mcp

Without uv: pip install docfig, then docfig mcp. Both read DOCFIG_API_KEY from the environment. check_transactions works without it. The hosted and local servers keep nothing: results are deleted after 60 minutes, and keys and file contents are never logged.

Endpoints

Method and pathWhat it does
POST /v1/statementsUpload one file (multipart/form-data, field file, optional password). Returns 202 with the statement id.
GET /v1/statements/{id}Status (queued, running, done, failed) and, when done, the result.
GET /v1/statements/{id}.xlsxExcel workbook: transactions, checks and flagged rows, as on the website.
GET /v1/statements/{id}.csvCSV: date, description, money out, money in, amount, balance, check.
DELETE /v1/statements/{id}Delete the result now instead of after 60 minutes.
POST /v1/combineJSON {"statements": ["st_...", ...]}: one set of accounts with missing months, overlaps, removed duplicates and balance carry-over checked. Add .xlsx or .csv to the path for the combined workbook.
GET /v1/files/{token}The signed Excel or CSV links in a result's downloads. They work without the key until expires_at, so share them only with people who may see the statement.
GET /v1/usagePages used and left this month.
GET /v1/openapi.jsonOpenAPI 3.1 spec (no key needed).

Response

Money is in the statement's currency as decimal numbers; amount is signed (money in positive). Account numbers are masked before the AI step, so only the last 4 digits come back.

{
  "id": "st_4kQx...", "object": "statement", "status": "done", "pages": 2,
  "result": {
    "bank": "Fernhollow Bank", "statement_type": "bank", "currency": "USD",
    "account": {"last4": "4417", "masked": "****4417"},
    "period": {"start": "2026-03-01", "end": "2026-03-31"},
    "opening_balance": 2450.18, "closing_balance": 3127.40,
    "transactions": [
      {"row": 1, "date": "2026-03-02", "description": "CARD PURCHASE GROCER",
       "money_in": null, "money_out": 64.20, "amount": -64.2, "balance": 2385.98,
       "page": 1, "flags": [], "notes": []}
    ],
    "checks": {"status": "reconciled", "rows": 38, "rows_passed": 38,
               "rows_flagged": 0, "balance_checks": 38, "balance_failures": 0,
               "totals": {"opening_closing": {"ok": true, "expected": 3127.4,
                                              "printed": 3127.4}}}
  }
}

checks.status is reconciled (every check passed), check (some rows are flagged; each has flags and plain-English notes) or unverified (the statement prints no balances or totals to check against).

Errors

Errors return JSON {"error": {"code": "...", "message": "..."}} with a stable code.

HTTPcodeMeaning
401missing_key, invalid_key, revoked_keyCheck the key header.
400no_file, empty_file, bad_requestNothing usable arrived.
413file_too_large, too_many_pagesOver 20 MB or 100 pages. Split the file.
415unsupported_typeSend a PDF, JPG, PNG or HEIC.
422password_required, password_wrongLocked PDF: send or fix password.
422infected, unreadableThe virus scan refused it, or no statement text could be read.
429rate_limited, too_many_jobsSlow down; wait for Retry-After seconds.
429monthly_limitThe month's pages are used; the error carries upgrade_url.
404not_foundUnknown id, or the result was already deleted.
409not_ready, failedDownload asked for before the conversion finished, or it failed.
503capacity, busyDaily capacity reached, or the converter is busy. Retry later.

Limits

What happens to the files

The same rules as the website: every upload is virus scanned, read in memory and dropped once its text is read. Account and card numbers are masked before the AI model sees the text. Results stay in memory for 60 minutes so they can be fetched and downloaded, then are deleted; DELETE removes them sooner. Nothing from a statement is written to disk or logged. Stored: the key's email, a hash of the key, and page counts. Details on the Security page.

Free reconciliation check for CSV files

Both SDKs include a command that checks a transactions table you already have, offline: no key, no network, no cost. It walks the running balance row by row and reports rows where the balance does not follow, debit and credit columns that look swapped, sign errors, duplicated rows, missing rows, dates out of order and totals that miss the opening or closing balance, each with a likely fix.

pip install docfig        # or: npm install -g docfig-api
docfig check statement.csv --opening 2450.18 --closing 3127.40
docfig check statement.csv --json    # for scripts; exit code 0 clean, 1 problems, 2 bad input

More