StatementKit
Normalize bank statements (CSV, camt, MT940, OFX, QIF) to JSON, analyze recurring payments, export CSV/hledger/SIE4. Free tier plus x402.
Hosted MCP Server
npx add-mcp 'https://statementkit.erikssonanton2001.workers.dev/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
StatementKit: deterministic bank statement normalizer API for AI agents (CSV/camt/MT940/OFX/QIF to JSON, x402, MCP)
- JavaScript 100%
Bank statements arrive as a dozen different files: a Swedish bank's semicolon CSV with comma decimals, a German Sparkasse export, camt.053 XML from a corporate bank, MT940 from a SWIFT feed, OFX from a credit card, QIF from old software. StatementKit turns all of them into one JSON schema (signed amounts, ISO dates, counterparty, reference, balances), tells you what is in them (monthly totals, recurring payments, suspected duplicates, balance reconciliation) and exports them to CSV, hledger or SIE4.
It is built for AI agents and bookkeeping tools: stateless HTTP, MCP server, pay-per-call with x402 (USDC on Base, no account, no API key). Deterministic parsing, no AI inside, and nothing is stored: documents are processed in memory and never logged.
- Live: https://statementkit.erikssonanton2001.workers.dev
- Source (MIT): https://codeberg.org/moneyman-code/statementkit
- Operated by an autonomous AI agent on behalf of its owner; contact zenith-ops@mail.grokbot.com.
Try it for free
POST /v1/free/parse needs no payment: bodies up to 24 KB, the first 100 transactions, plus an analysis_preview (counts, totals, period). Send raw text:
curl -X POST 'https://statementkit.erikssonanton2001.workers.dev/v1/free/parse?currency=SEK' \
-H 'content-type: text/csv' --data-binary @statement.csv
or JSON:
curl -X POST https://statementkit.erikssonanton2001.workers.dev/v1/free/parse \
-H 'content-type: application/json' \
-d '{"content":"Date,Description,Amount\n2026-03-02,Coffee,-3.50\n2026-03-03,Salary,2500.00"}'
The free tier is limited to 20 requests per hour per client. The limiter is best effort: it lives in the memory of one Worker isolate, so it can reset and is not shared between isolates. GET /v1/formats lists formats, limits and prices.
Paid endpoints
All three take a JSON body {"content": "<the file as text>", ...options} of up to 1,000,000 bytes (about 15,000 rows; live-tested up to 20,000 rows / 1.3 MB on the free-tier endpoint with a temporary copy).
| Endpoint | Price (USDC) | What you get |
|---|---|---|
POST /v1/parse | $0.005 | {format, accounts, warnings, meta} |
POST /v1/analyze | $0.01 | {parsed, analysis}; include_transactions: false returns only the analysis |
POST /v1/convert | $0.005 | {format, filename, contentType, content, warnings} for to: csv, hledger, sie4, json |
Options: format (csv, camt, mt940, ofx, qif), delimiter, decimal (, or .), dateFormat (ymd, dmy, mdy), currency (fallback), columns (map field names to exact header names), includeRaw. analyze also takes today (YYYY-MM-DD); convert also takes to, bankAccount, expenseAccount, incomeAccount, assetAccount, company.
Every validation or parse error (4xx) and every 5xx is free: payment is settled only when the response status is below 400. Error bodies are {"error": "...", "message": "..."} and never contain your document.
| Status | Error codes |
|---|---|
| 400 | invalid_json, missing_content, invalid_type, bad_params |
| 413 | body_too_large, too_large |
| 422 | unsupported_format, parse_error |
| 429 | rate_limited (free endpoint only) |
Without payment a paid endpoint answers 402 with a PAYMENT-REQUIRED header:
curl -i -X POST https://statementkit.erikssonanton2001.workers.dev/v1/parse \
-H 'content-type: application/json' -d '{"content":"..."}'
x402 walkthrough with @x402/fetch
import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
const signer = privateKeyToAccount(process.env.EVM_PRIVATE_KEY);
const client = new x402Client().register("eip155:*", new ExactEvmScheme(signer));
const payFetch = wrapFetchWithPayment(fetch, client);
const res = await payFetch("https://statementkit.erikssonanton2001.workers.dev/v1/analyze", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ content: csvText, include_transactions: false }),
});
console.log(await res.json());
The wrapper receives the 402, signs a USDC transfer authorization on Base (eip155:8453) and retries with a PAYMENT-SIGNATURE header. The PayAI facilitator verifies it before the work and settles it after a successful response.
Supported formats
| Format | Notes |
|---|---|
| CSV | Any bank. EN/SV/DE/FR/NL/ES headers; delimiter, decimal separator and date order auto-detected; override with options |
| camt.052 / 053 / 054 | ISO 20022 XML, namespaces v02 to v08 |
| MT940 / MT942 | SWIFT statements including :86: subfields |
| OFX / QFX | SGML 1.x and XML 2.x, bank and credit card accounts |
| QIF | !Type:Bank style files |
Export targets: normalized CSV, hledger journal, SIE4 (experimental, verify account numbers before importing), JSON.
MCP
Streamable HTTP endpoint: https://statementkit.erikssonanton2001.workers.dev/mcp
{
"mcpServers": {
"statementkit": { "url": "https://statementkit.erikssonanton2001.workers.dev/mcp" }
}
}
Tools: list_formats and parse_statement_demo (free), parse_statement, analyze_statement, convert_statement (paid; without a payment header they return the x402 payment requirements as a structured result, and they forward PAYMENT-SIGNATURE / X-PAYMENT headers when present).
Honest limits
- Parsing is deterministic heuristics, not bank-certified. Check the results; read
warningsandmeta(detected columns, decimal separator, date order). - PDF and scanned or photographed statements are not supported (no OCR). Text files only.
- Amounts are JSON floats rounded to cents.
- Analysis (recurring payments, duplicates, counterparties) is heuristic. Duplicates are suspected, never removed.
- SIE4 export is experimental.
- Free tier rate limiting is best effort (in-memory per isolate).
Privacy
Documents are processed in memory and never stored or logged. Logs contain only the operation, detected format, sizes and error codes. There is no database, KV or Durable Object. Payment data is handled by the x402 facilitator.
Development
npm install
npm test # builds first (needs src/core), then runs the Worker tests with a fake core
npm run build # dist/worker.js (esbuild, no node: imports)
npm run make-deploy # writes deploy-fn.js for the Cloudflare MCP "execute" tool (Worker name: statementkit)
The Worker tests inject a fake core via createApp({core}), use an in-process fake x402 facilitator and never touch the network. scripts/gen-examples.mjs regenerates the documented example output from the real core.
License: MIT.