How to Import Bank Statements Into an Expense Tracker
Use a CSV or PDF bank statement with an AI agent, review duplicates and transfers, then reconcile the balance before importing approved rows.
A closed bank or card statement can look ready to import while still containing several traps. Some transactions may already be in your ledger because you recorded them from receipts. A card payment may look like new spending even though the purchases were recorded weeks earlier.
The safe way to import bank statements into an expense tracker is to preserve the bank's source, let an AI agent prepare a reviewable draft, approve only the rows you understand, then reconcile every affected account.
Expense Budget Tracker doesn't have a native one-click CSV or PDF upload wizard, and it doesn't keep a persistent connection to your bank. This workflow uses an external AI agent or script to read the statement and interact with the tracker. The agent handles the file; you review and control the exact ledger write.
![]()
Start with one source you can return to
Keep the original statement unchanged. Work from a copy and give the agent the exact file path or attachment, account, currency, and closed date range.
One file should represent one account and one statement period whenever possible. If a bank export combines two cards, mixes pending and posted activity, or overlaps several periods, split the review into explicit sections before normalizing transactions. Otherwise a later balance mismatch has too many places to hide.
Use posted transactions for a closed-period import. Pending items can change amount, description, or date. Put them in a separate watch list rather than treating them as settled history.
The file format changes the extraction risk. A CSV bank statement import starts with structured fields; a PDF bank statement import may start with extracted text or pixels.
- A structured CSV usually gives the cleanest starting point, but the agent still has to confirm the delimiter, date format, decimal separator, currency, and whether amounts use separate debit and credit columns or one signed field.
- A text-based PDF may preserve readable transaction text while losing column alignment. Give each draft row a page number plus a transaction sequence or line reference so you can trace it back.
- A scanned or image-only PDF needs OCR or image understanding. Cropped pages, faint print, and merged columns can make exact extraction impossible. If a date, amount, currency, or balance is unclear, stop and request a better file or manual confirmation.
An AI bank statement parser is only as reliable as the supplied file and the chosen tool's access to it. A neat table isn't evidence that every row or digit was extracted correctly.
Use this exact review table
The agent should create a draft outside the ledger first, with exactly one review row for every posted source transaction—including duplicates and exclusions. Use these columns in this order:
| Column | What belongs there |
|---|---|
source_ref |
CSV row number, or PDF page plus transaction sequence or line reference |
posted_at |
Posted date and time if the source provides them; include a time zone only when the source supplies it or the user confirms it |
raw_description |
Source text exactly as shown, without merchant cleanup |
statement_amount |
Original debit, credit, or signed amount exactly as shown in the source |
currency |
Source currency; never infer it silently from a symbol |
ledger_amount |
Amount normalized to the tracker's sign convention |
target_account |
Exact existing account selected from the workspace |
proposed_kind |
Exact value allowed by the live schema for this movement |
proposed_category |
Exact existing category, or the live schema's required no-category value for a transfer |
transfer_match |
Counterpart account and source reference, or unresolved |
duplicate_candidate |
Matching ledger row and evidence, or none found |
uncertainty |
Any unresolved text, date, amount, account, currency, or classification issue |
decision |
insert, match existing, exclude, or needs review |
For example, a row already recorded from a receipt should remain in the table with match existing. It should not disappear from the evidence, and it should not become a second ledger entry.
The raw description and statement amount matter even after the agent produces cleaner fields. They are the quickest route back to the source when a proposed category or sign looks wrong.
Connect the agent, then inspect reality
For a terminal agent or direct HTTP script, begin with:
GET https://api.expense-budget-tracker.com/v1/
Follow the discovery response, complete the email-code flow, and store the returned long-lived ApiKey outside chat memory. Authenticated requests use Authorization: ApiKey <key>. The Agent Setup guide covers the complete onboarding sequence.
Before constructing any SQL, the agent should:
- Confirm the signed-in account and exact workspace.
- Select the intended workspace or send its ID with the request.
- Inspect
GET /v1/schemafor the current allowed relations and columns. - Query the exact target account, its currency, existing categories, and the last trusted reconciliation point through
POST /v1/sql/query. - Query existing ledger activity for the statement period and its duplicate-check boundary before proposing a write.
Do not copy a column list from this article into an import script. The live schema is the contract. The API reference documents the endpoints and SQL rules, while /v1/schema tells the agent what can be written now.
An MCP-capable client can instead connect to:
https://mcp.expense-budget-tracker.com/mcp
That path uses browser OAuth rather than an ApiKey. Request expenses:read to use list_workspaces, get_schema, and sql_query. Add the optional expenses:write scope only when the client is ready to call sql_execute. The MCP Connector guide explains the connection and approval model.
Both paths cap returned query rows at 100. The direct API also caps affected rows at 100 per mutation request. If a period contains more rows, divide reads into non-overlapping date or source boundaries and show that every posted statement row appears exactly once in the review table.
Draw a practical duplicate boundary
Query the target account for the full statement period plus three calendar days on each side. The small overlap catches receipt entries and imported rows whose purchase and posting dates differ. It is a review boundary, not proof that two rows are the same.
Treat an existing stable bank transaction identifier as strong duplicate evidence when both source and ledger contain it. Without one, flag a candidate when all of these match:
- target account
- currency
- exact signed amount
- date within three calendar days
Then compare the raw description, cleaned counterparty, reference text, and any receipt evidence. Two real purchases can have the same amount at the same merchant, so the agent should return candidates rather than silently deleting, updating, or skipping them.
This is where statement work meets receipt work. A receipt-scanner workflow proves the details of an individual purchase: merchant, items, tax, tip, and total. A statement proves movement on an account and may provide opening and closing balances. When both describe the same purchase, match them; don't record it twice.
Let your ledger categories guide the draft
Read the categories already used in the selected workspace and map statement rows to those names. A merchant description can suggest a category, but it cannot prove one. A payment processor, marketplace, or unfamiliar transfer label is especially weak evidence.
Put unclear rows in needs review. Don't invent a category to make the table look complete. The useful output is a short uncertainty list attached to specific source references, not a confident guess buried among approved rows.
Refunds deserve the same care. Keep their source sign and account movement visible, then map them according to the workspace's existing treatment. Don't quietly label every positive amount as income.
Keep transfers from becoming fake spending
An internal transfer changes account balances without creating consumption. If both accounts are inside your tracked boundary, represent both account movements using the relationship required by the live schema. The source leg leaves one account, the counterpart enters the other, and the pair should not inflate spending.
This covers the common cases:
- checking to savings is an internal transfer when both accounts are tracked
- a credit-card purchase is spending on the card account
- the later payment from checking to that tracked card is a transfer, not the purchase again
- a separately stated bank fee is spending, even when it appears beside a transfer
- cross-currency transfer legs keep the actual amount and currency from each account's own statement; don't invent a conversion amount for the missing side
If the counterpart account or statement is missing, leave transfer_match as unresolved. Do not manufacture the other leg or approve the row as ordinary spending just to finish the batch. The detailed decision boundary is in Do Bank Transfers Count as Expenses?.
An account outside your tracked budget needs an explicit policy. A payment crossing that boundary may be a purchase, debt settlement, investment contribution, reimbursement, or something else. The word “transfer” in the bank description doesn't settle it.
Approve a batch, not a vague intention
Once every posted source row has a decision—even if that decision is needs review—the agent should show:
- the workspace, statement account, currency, and period
- posted source-row count and the statement's debit, credit, or signed totals
- rows matched to existing ledger entries
- rows excluded from import and why
- unresolved duplicate or transfer candidates
- the exact SQL for one small batch, constructed from the live schema
- the expected affected-row count
Use an approval this specific:
I approve statement import batch [number] only.
Workspace: [name and ID]
Statement account: [account name and ID]
Period: [start through end]
Approved source refs: [exact list]
Expected affected rows: [count]
Execute only the exact SQL shown in the latest preview.
Do not change any other row. Stop after read-back verification.
If the workspace, account, SQL, source list, or expected effect changes, the approval no longer applies. Ask for a new preview and a new approval.
Write in small batches and read each one back
For the direct API, send one approved INSERT, UPDATE, or DELETE statement per request to POST /v1/sql/execute. For MCP, use sql_execute with expenses:write. A multi-row insert can still be one statement, but keep batches small enough that a human can compare every row with the review table. Start with the smallest coherent group—one transfer pair or a handful of ordinary rows. Continue only after its read-back is clean and the next batch has its own preview and approval.
After each batch, query those exact rows again through /v1/sql/query or sql_query. Compare the stored account, timestamp, signed amount, currency, kind, category, and transfer relationship with the approved preview, then map each read-back row to its approved source reference. An HTTP success response does not prove that the ledger contains what you intended.
Don't mix cleanup into the import. If read-back exposes a wrong value or an existing row needs correction, prepare a separate exact UPDATE or DELETE, explain its effect, and request fresh approval.
Reconcile every account the import touched
For a statement that supplies opening and closing balances, first use the statement's own debit, credit, and balance convention:
expected source closing balance = source opening balance + sum(posted source movements)
For the tracker, calculate forward from its last known-good account checkpoint rather than assuming the statement's first date is already reconciled:
expected tracker balance at cutoff = last known-good tracker balance
+ sum(signed ledger movements after that checkpoint through the cutoff)
Translate any bank-specific debit, credit, or card-debt presentation into the tracker's sign convention and show that translation. The expected tracker balance must then equal the normalized statement closing balance for that account. Also compare the imported source refs with the review table: every posted statement row should be inserted, matched to an existing row, or explicitly excluded.
If the import created or matched both sides of an internal transfer, reconcile both affected accounts against their own statements or known-good balance checkpoints. One matching source account can still hide a fabricated or duplicated destination leg.
A CSV transaction export may not include opening and closing balances. In that case, use a separate bank-provided balance at an exact cutoff or a previous known-good reconciliation point. Without either, you can verify row coverage and totals, but you cannot claim full bank statement reconciliation.
The stop condition is simple: if any account has an unexplained balance difference, an unresolved source row, an uncertain amount or currency, or an ambiguous duplicate or transfer, stop. Don't add a balancing transaction. Preserve the draft and investigate the specific gap with the budget reconciliation workflow.
Know where the statement data goes
This is an expense tracker without bank linking, not a promise that the statement stays on your machine.
The AI client or model provider you choose may process the CSV or PDF and the financial text it contains. Review that provider's data-handling terms and give it only the file access needed for the job. In this documented workflow, the agent sends SQL requests and approved structured writes to the tracker, then receives selected structured rows in return. The original statement file is not uploaded to the tracker.
Self-hosting Expense Budget Tracker doesn't automatically self-host the AI agent, OCR tool, or model provider. Those are separate systems with separate data boundaries. If avoiding persistent bank access is the priority, read how a budget app can work without bank linking and choose each part of the workflow accordingly.
A prompt you can reuse
Import the closed bank or card statement at [file path or attachment] into Expense
Budget Tracker. This is for workspace [name] and account [name], covering [dates].
Do not write yet. Preserve the original file. Identify whether it is structured CSV,
text-based PDF, or scanned/image PDF, and report any extraction limits. Exclude pending
activity. Build the exact review table from this article, keeping a source reference,
raw description, original amount, normalized ledger amount, and uncertainty for every
posted row.
For direct HTTP, begin at https://api.expense-budget-tracker.com/v1/, follow its
discovery response, use the returned or stored long-lived ApiKey, confirm /me and the
exact workspace, inspect /v1/schema, and read via /v1/sql/query. For MCP, use
list_workspaces, get_schema, and sql_query with expenses:read; request expenses:write
only for an approved sql_execute write.
Before proposing SQL, query the existing target period plus three calendar days on each
side. Respect the 100-row result limit by using non-overlapping boundaries when needed.
Flag duplicate candidates; never skip them silently. Reuse existing categories. Treat
credit-card payments and movements between tracked accounts as transfers, link both
actual account movements according to the live schema, and leave missing counterparts
unresolved rather than inventing them.
Show the completed review table, row counts, source totals, duplicate evidence, transfer
pairs, exclusions, and all unresolved items. Then show the exact live-schema SQL for one
small batch and its expected affected-row count. Wait for my batch-specific approval.
After approval, execute only that one INSERT, UPDATE, or DELETE statement through
/v1/sql/execute or sql_execute. Re-query the exact rows and compare them with the approved
preview. Reconcile every affected account to its statement closing balance or an exact
known-good balance checkpoint. Stop without adding a balancing row if anything remains
unexplained.
The result is concrete: the original evidence, a decision for every source row, an approved ledger change, and balances you can explain.