The importers/ directory contains standalone CLI tools for getting data into mibudge. They authenticate with the mibudge REST API using a normal username and password – they do not require direct access to the server, the database, or Django management commands. Anyone with a mibudge account can run them.
These tools will eventually live in their own repository. For now they share the project’s pyproject.toml and are run from the repo root with uv run.
| Tool | Command | Purpose |
|---|---|---|
| Statement importer | python -m importers import |
Parse bank statement files (OFX/QFX or BofA CSV) and POST new transactions to mibudge |
| Budget backfill | python -m importers backfill_budget |
Interactively allocate historical transactions to a budget, month by month |
All tools accept connection and credential flags. In practice, set them once in a .env file at the repo root:
MIBUDGE_URL=https://your-mibudge-host
MIBUDGE_USERNAME=you@example.com
MIBUDGE_PASSWORD=yourpassword
Alternatively, pull credentials from HashiCorp Vault:
export VAULT_ADDR=https://vault.example.com
export VAULT_TOKEN=s.xxxx
uv run python -m importers --vault-path mibudge/prod import stmt.ofx
The Vault secret must have keys url, username, and password.
# Auto-detect mkcert root CA:
uv run python -m importers --trust-local-certs import stmt.ofx
# Or specify a CA bundle explicitly:
uv run python -m importers --ca-bundle "$(mkcert -CAROOT)/rootCA.pem" import stmt.ofx
Parses one or more bank statement files and imports their transactions into mibudge. Statements are validated for internal consistency before any API calls are made. Already-imported transactions are detected and skipped – re-running the same file is safe.
| Extension | Parser | Carries account identity? |
|---|---|---|
.ofx, .qfx |
OFX/QFX | Yes – ACCTID and account type from the file |
.csv |
Bank of America CSV | No – account must be specified or created via flags |
For an existing account, the OFX ACCTID is matched against the account’s stored account number automatically. This is the common case for monthly imports:
uv run python -m importers import ~/Downloads/*.ofx
Multiple files for the same account can be passed in one run. They are sorted by date and a warning is emitted if consecutive statements have a gap.
The account type and number come from the OFX file; only the display name and parent bank are needed:
# List banks to find the UUID:
uv run python -m importers banks
uv run python -m importers import --create-account \
--name "Personal Checking" \
--bank <bank-uuid-or-name> \
~/Downloads/2025-*.ofx
CSV files carry no account identity, so all account details must be supplied:
uv run python -m importers import --create-account \
--name "Joint Checking" \
--bank <bank-uuid-or-name> \
--account-type checking \
--account-number 1234567890 \
~/Downloads/stmt.csv
Since CSVs have no ACCTID to match on, the account must be identified explicitly:
uv run python -m importers import --account <account-uuid-or-name> \
~/Downloads/stmt.csv
Parse, validate, and check for duplicates without posting anything:
uv run python -m importers import --dry-run ~/Downloads/*.ofx
| Flag | Purpose |
|---|---|
positional paths or -f, --file |
Statement files (repeatable, shell globs accepted) |
--account, -a |
Target account by UUID or name fragment |
--create-account |
Create the destination account before importing |
--name |
Account display name (create mode) |
--bank |
Parent bank UUID or name (create mode) |
--account-type checking\|savings\|credit |
Account type (create mode, CSV only) |
--account-number |
Account number (create mode, CSV only) |
--dry-run, -n |
Validate and dedup-check without importing |
--run-funding |
Run the funding engine after a successful import |
--verbose, -v |
Debug logging |
--plain |
Disable rich terminal output (auto-detected when not a TTY) |
The importer does not trigger funding automatically. If you want to fund budgets immediately after importing, pass --run-funding:
uv run python -m importers import ~/Downloads/*.ofx --run-funding
This calls POST /api/v1/bank-accounts/<id>/run-funding/ after the import completes and prints the result (number of transfers, any warnings, skipped budget names). The flag is silently ignored on --dry-run runs.
Without --run-funding, the funding engine will still run automatically at 3:00 AM each night.
The importer resolves the destination account with this precedence:
--account flag (explicit, wins everything)ACCTID matched against existing account numbers (OFX/QFX only)--create-account (creates a new account)Ambiguous or conflicting combinations are rejected with a clear error.
Before any API calls, each statement is checked for internal consistency:
beginning + credits + debits == endingA file that fails its own consistency check is rejected before touching the server.
Interactively allocates historical transactions to a single budget, month by month. Use this when you’ve added a new budget and want to assign past transactions to it without doing it one-by-one in the UI.
uv run python -m importers backfill_budget \
--account "Personal Checking" \
--budget "Groceries"
The tool fetches all transactions for the account, groups them into monthly periods, and steps through each unallocated transaction asking whether it belongs to the budget. Vendor names are extracted from raw bank descriptions, and you can save yes/no rules per vendor so repeat merchants are handled automatically in future runs.
Transactions, allocations, and internal transactions are all fetched once at startup. Any transactions imported while the script is running will not appear in the current session — re-run after importing new transactions to pick them up.
| Key | Action |
|---|---|
y |
Allocate this transaction to the budget |
n |
Skip this transaction |
a |
Always allocate this vendor to this budget (saves rule) |
s |
Skip all remaining transactions from this vendor in this session |
q |
Quit and save rules |
Saved rules are stored in ~/.mibudge/vendor_rules.json keyed by budget UUID.
At the end of each monthly period, the tool automatically funds the budget to prepare it for the next period:
target_balancefunding_amount, up to the cap ceilingRe-running the script for a budget you have already backfilled is safe. Before processing begins, the tool fetches all existing funding transfers (Unallocated → target budget InternalTransactions) and records their effective dates in a funded_dates set. Any period whose funding date is already in that set is silently skipped – no duplicate transfer is created. Only transactions that are still allocated to Unallocated are shown for review; anything already allocated to another budget is ignored.
If reassigning transactions causes the budget’s balance to go negative (because spending now exceeds the funding that was originally recorded), that is not corrected automatically. You are responsible for making a manual internal transfer to bring the budget back to zero or above.
| Flag | Purpose |
|---|---|
--account, -a |
Bank account by UUID or name (required) |
--budget, -b |
Target budget by UUID or name (required) |
--verbose, -v |
Debug logging |
--plain |
Disable rich terminal output |
Full developer documentation – parser internals, adding new bank formats, multi-file dedup semantics, OFX balance derivation edge cases – is in importers/README.md.
To run the importer test suite:
uv run pytest importers/tests/ -v