A Python CLI tool that syncs financial transactions from the Plaid API to Beancount format. Downloads transactions from connected bank accounts and investment accounts, and outputs them as properly formatted Beancount entries with automatic expense categorization.
- Incremental Sync: Uses Plaid's cursor-based sync to efficiently fetch only new transactions
- Investment Support: Handles investment transactions (buy, sell, dividends, sweeps, transfers)
- Smart Categorization: Maps transactions to expense accounts using:
- Plaid's personal finance categories
- Custom payee-based rules
- Multi-Account: Organizes transactions into separate files per account
- Recategorization: Re-categorize existing transactions when rules change
- Permission Management: Web-based interface to update Plaid connection permissions
- Python 3.10 or higher
- Plaid API credentials (client_id and secret)
- Beancount 2.3.0+
- Clone this repository:
git clone <repository-url>
cd plaid2beancount- Create and activate a virtual environment:
# Create virtual environment
python3 -m venv venv
# Activate virtual environment
# On macOS/Linux:
source venv/bin/activate
# On Windows:
venv\Scripts\activate- Install dependencies:
pip install -r requirements.txt- Create a configuration file at
~/.config/plaid2text/config:
[PLAID]
client_id = your_plaid_client_id
secret = your_plaid_secretYour root beancount file must include account metadata for Plaid integration:
2020-01-01 open Assets:Bank:Checking
plaid_account_id: "abc123"
plaid_item_id: "item_xyz"
plaid_access_token: "access-production-..."
transaction_file: "accounts/Bank/Checking.beancount"
short_name: "Chase Checking"
2020-01-01 open Expenses:Groceries
plaid_category: "FOOD_AND_DRINK_GROCERIES"
payees: "whole foods, trader joes, safeway"
Required metadata per account:
plaid_account_id: Plaid's account identifierplaid_item_id: Plaid's item (institution) identifierplaid_access_token: Access token from Plaid Linktransaction_file: Relative path where transactions should be writtenshort_name: Human-readable account name
Expense categorization (optional):
plaid_category: Maps Plaid's category to this expense accountpayees: Comma-separated list of merchant names (payee rules override category rules)
Add this to your root beancount file:
include "plaid_cursors.beancount"
The tool automatically manages sync cursors in this file to enable incremental updates.
Note: Make sure to activate your virtual environment before running any commands:
source venv/bin/activate # macOS/LinuxDownload new transactions from Plaid and write them to your beancount files:
python main.py --sync-transactions --root-file path/to/root.beancountThis will:
- Fetch new transactions using saved cursors (incremental sync)
- Categorize transactions based on your rules
- Write transactions to individual account files
- Update cursors for next sync
Update expense categories for existing transactions when your rules change:
python main.py --recategorize --root-file path/to/root.beancountOptional date filters:
python main.py --recategorize \
--start-date 2024-01-01 \
--end-date 2024-12-31 \
--root-file path/to/root.beancountIf Plaid connections expire (ITEM_LOGIN_REQUIRED error), reauthorize via web interface:
python main.py --update-permissions --root-file path/to/root.beancountThis opens a browser where you can select the expired item and re-authenticate with your bank.
View account details from Plaid:
python main.py --show-accounts --root-file path/to/root.beancountDisplays account numbers, types, balances, and institution information.
--sync-transactions, -s Sync transactions and generate beancount entries
--recategorize, -r Re-categorize existing transactions based on current rules
--update-permissions, -u Update Plaid item permissions via web interface
--show-accounts, -a Show Plaid account information for a selected item
--start-date YYYY-MM-DD Start date for recategorization
--end-date YYYY-MM-DD End date for recategorization
--config-file PATH Path to config file (default: ~/.config/plaid2text/config)
--root-file PATH Path to root beancount file (required)
--debug Debug mode: fetch only first batch of transactions
root.beancount # Root file with account definitions
plaid_cursors.beancount # Auto-generated cursor tracking
accounts/
Bank/
Checking.beancount # Individual account transactions
Savings.beancount
CreditCard/
Chase.beancount
Investments/
Brokerage.beancount
- Load Configuration: Reads Plaid credentials and beancount metadata
- Fetch Accounts: Gets account info from Plaid to validate connections
- Incremental Sync: Uses cursors to fetch only new transactions since last sync
- Categorization: Applies payee rules (priority) or category mappings
- Render: Converts Plaid transactions to Beancount format
- Write: Appends new transactions to account files (deduplicates by transaction ID)
- Update Cursors: Saves new cursors for next sync
Transactions are categorized using two methods (in priority order):
- Payee-based rules: Exact or partial match on merchant/payee name (case-insensitive)
- Category-based rules: Maps Plaid's
personal_finance_category.detailedfield
Example:
; Payee rule (highest priority)
2020-01-01 open Expenses:Coffee
payees: "starbucks, peet's coffee"
; Category rule (fallback)
2020-01-01 open Expenses:Groceries
plaid_category: "FOOD_AND_DRINK_GROCERIES"
The tool handles complex investment transaction types:
- Buy: Cash → Security (with cost basis)
- Sell: Security → Cash (with capital gains posting)
- Dividend: Income:Dividends → Cash
- Sweep In/Out: Movement between cash and money market funds
- Transfer: External transfers
Investment accounts use sub-accounts:
Assets:Investments:Brokerage:CashAssets:Investments:Brokerage:VTSAXAssets:Investments:Brokerage:AAPL
If you see TRANSACTIONS_SYNC_LIMIT errors:
- Wait a few minutes before retrying
- The tool now properly saves cursors to avoid redundant API calls
- Use
--debugflag to limit fetches during testing
Your Plaid connection expired. Run:
python main.py --update-permissions --root-file path/to/root.beancountThe tool automatically deduplicates using plaid_transaction_id metadata. If you see duplicates:
- Check that transaction files include the metadata line
- Verify cursors are being saved in
plaid_cursors.beancount
Transactions without matching categorization rules go to Expenses:Unknown. Add rules to your root beancount file:
2020-01-01 open Expenses:Unknown
Always activate the virtual environment before running commands:
# Activate
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
# Deactivate when done
deactivatepytest
pytest tests/test_import.py
pytest tests/test_recategorize.pyEnable debug logging:
python main.py --sync-transactions --root-file path/to/root.beancount --debugThis:
- Enables verbose logging
- Fetches only the first batch of transactions (avoids hitting rate limits during testing)
If you need additional development tools (linting, type checking):
pip install -e ".[dev]"This installs the optional dev dependencies defined in pyproject.toml.
This project is licensed under the MIT License.