Building a Read-Only LLM Personal Finance Assistant: Architecture and Security Practices

    This article presents the design of a read-only personal finance analysis assistant powered by an LLM. It covers why read-only access should be the architectural baseline, how to connect account data safely and handle tokens, and how to turn natural-language questions into constrained read-only queries. It also covers detecting spending anomalies, generating dashboards, explaining interest correctly, and isolating and minimizing sensitive data, followed by a pre-launch security checklist. The goal is useful analysis without risking user funds.

    1. How Risky Is a Read-Only Design, Really?

    A personal finance analysis assistant must first settle one question of positioning: it is a tool for querying and analyzing financial information, not for moving money. That boundary defines the permission baseline for the entire system.

    • What it can do: query balances, aggregate transaction history, break spending down by category, detect recurring bills, and generate charts and dashboards.
    • What it explicitly cannot do: initiate transfers, manage or rebalance assets, or execute any write operation or investment instruction.

    Making read-only access a first principle is not merely a compliance posture. Architecturally, it removes the channel through which funds could ever be written. To put it precisely: when access is read-only, the direct consequence of a model error is a wrong analytical conclusion, not a direct loss of money. That does not mean “the worst case is a single wrong conclusion,” though. A wrong conclusion can still mislead a user’s repayment, budgeting, or investment decisions. Privacy risks from leaked tokens or a data breach at the aggregation service are a separate, parallel risk line that requires its own safeguards (see Sections 3 and 7).

    On output reliability, the source should be cited accurately. On its official usage policy and documentation pages (https://www.anthropic.com/legal/aup; the specific terms on the official page apply), Anthropic notes that model output may be inaccurate and recommends that users independently verify important information. The in-product notice “treat bank records as authoritative for key figures” is a product design recommendation proposed in this article, not a paraphrase of Anthropic’s wording, and the two should not be conflated.

    2. How Do You Connect Account Data Securely?

    The common industry approach is to bring in a financial data aggregation service: a third-party data aggregation layer built on open banking APIs. It handles connections to individual financial institutions, authentication, and data standardization. The statement “minimize credentials” is, however, only half the story, because two distinct modes must be separated:

    Access mode Where credentials go Main risk
    Open banking API / OAuth authorization The user authorizes access at the bank; the third party receives a read-only token Token scope or validity period configured improperly
    Screen scraping / credential hosting The user gives their bank account credentials to the aggregation service Credential retention, redistribution, or leakage
    Statement file import (CSV/PDF/statements) No account credentials; files are uploaded only Files stored in plaintext; files redistributed

    The assistant itself never handles the user’s bank password. However, the aggregation service itself usually needs to receive the user’s bank credentials, and that point cannot be left out. Vendor selection must therefore evaluate the provider’s compliance credentials, its data retention and redistribution policies, and whether it supports granular read-only scopes. Record the evaluation conclusions in internal risk-control documentation.

    In the data model, fields should be explicitly narrowed:

    1. # Illustrative read-only data model: expose only fields needed for queries and analysis
    2. class FinancialAccount:
    3. bank_name: str # Institution name
    4. account_name: str # Account name
    5. account_type: str # Savings / credit / debit
    6. account_last4: str # Last four digits of account number (masked)
    7. balance: Decimal # Current balance
    8. credit_limit: Decimal # Credit limit (credit accounts only)
    9. class Transaction:
    10. timestamp: datetime # Transaction time
    11. amount: Decimal # Amount; sign indicates direction of funds
    12. merchant_ref: str # Internal merchant reference (replaced with a category label before entering the model)
    13. merchant_name: str # Plaintext merchant name; stored locally only, never included in prompts
    14. category: str # Spending category
    15. status: str # Status: posted / pending / reversed

    Token governance boils down to three things: read-only least privilege; short-lived tokens plus refresh-token rotation; and revocation that propagates (an unlinking request must also revoke the authorization at the aggregation service, and you must verify that subsequent data pulls actually stop). Historical depth should be limited to a finite window (for example, at most two years), with the rule of thumb being “the smallest window that still covers year-over-year trend analysis” rather than indefinite retention.

    Common mistakes: storing the aggregation service’s raw responses as-is and keeping plaintext merchant names indefinitely; and, when unlinking, deleting only the local association without revoking the upstream authorization.

    3. How Do You Turn Natural Language into Executable Queries?

    Users don’t ask in financial jargon. They ask things like “where did all my money go this month?” or “how much will I have left before payday?” Turning questions like these into reliable queries requires a clear pipeline:

    1. User's natural-language question
    2. ↓ Intent recognition + entity extraction (time range / account scope / metric / grouping dimension)
    3. ↓ Parameter validation and clarification (ask back when information is missing or a relative time cannot be anchored)
    4. ↓ Generate a structured query plan (a constrained DSL, not SQL)
    5. ↓ Read-only data access → executed by the deterministic computation layer
    6. ↓ Assemble results + explain terminology + show confidence cues → return to user

    The query contract must be fixed, and the model must never generate or execute SQL:

    1. {
    2. "metric": ["spend_total", "spend_by_category"],
    3. "time_range": {"start": "2026-08-01", "end": "2026-08-31", "anchor": "absolute"},
    4. "account_scope": {"account_ids": ["acc_****1234"], "types": ["credit", "debit"]},
    5. "group_by": ["category"],
    6. "filters": [{"field": "status", "op": "in", "value": ["posted"]}],
    7. "top_n": 10,
    8. "max_rows": 500,
    9. "currency": "CNY"
    10. }

    Example metric whitelist: balance_current, spend_total, spend_by_category, income_total, recurring_bill_detected, credit_utilization. Any metric outside the whitelist is refused, and execution falls back to clarification.

    Relative time must first be anchored to absolute dates. “How much will I have left before payday?” is handled in this order: identify historical payday dates from recurring deposits → take the most recent one → anchor it to a concrete date. If identification fails, or if multiple suspected paydays exist, ask the user to confirm instead of guessing. Likewise, “this month” must be resolved to a start and end date using the user’s time zone and the natural month boundary.

    A full-chain example:

    1. User question: “Where did my money go last month, and how much will I have left before payday?”
    2. Intent and entities: metric = spend_by_category + available_before_payday; time_range = 2026-07-01 to 2026-07-31; payday anchored to 2026-07-10 (mean of the last 6 historical deposits, confidence 0.9).
    3. Query plan: two constrained DSL calls (category aggregation + cash-flow time series).
    4. Data retrieval results: total spending ¥12,480; dining ¥3,120 (25%), rent and utilities ¥5,600 (45%), transportation ¥1,240, other ¥2,520; salary of ¥18,000 deposited on July 10.
    5. Calculation basis: funds available before payday = net cash flow around payday − pending amounts − user-recorded planned spending. Code performs the calculation, not the model’s “mental math.”
    6. Final answer: “Spending in July was ¥12,480, of which rent and utilities accounted for 45%, which is on the high side. Before payday on July 10 (inferred from historical deposits), you are expected to have about ¥2,340 available, after deducting ¥1,200 in pending transactions and ¥800 in planned spending. Treat bank records as authoritative for the figures.”

    The key constraint can be stated in one sentence: the model is responsible for understanding intent and orchestrating queries, while data retrieval and calculation are delegated to the deterministic code layer. Precise arithmetic, such as summing amounts or deducting balances, is never left to the model’s “mental math.”

    Common mistakes: letting the model generate SQL directly; defaulting to “the last 30 days” when a time parameter is missing without telling the user; and inserting merchant names into prompts as-is.

    4. How Do You Detect Unusual Spending?

    Anomaly detection is a rule engine that can be layered on top of structured data. Its criteria must be spelled out, because false positives erode user trust:

    Anomaly type Example detection criteria Notes
    Suspected duplicate charge Same merchant + amount difference ≤ ¥1 + interval ≤ 72 hours + both posted Thresholds are tunable; subscription renewals must be excluded
    Recurring bill price increase Same merchant, 2 consecutive periods with a month-over-month increase ≥ 5% and a cumulative increase ≥ ¥10 Apply time-series slope detection
    Unexpected new fee First appearance of a new merchant + amount ≥ the user-defined threshold (default ¥100) A first appearance alone is not an anomaly
    Subscription detection Same day, amount variation ≤ 2%, cycle of 28–31 days, 3 consecutive periods Used to surface subscriptions the user may have forgotten to cancel

    Evaluation method: compute precision/recall on historical labeled samples, prioritizing control of the false positive rate (target < 5%). Also provide a “this is not an anomaly” feedback option in the UI. That feedback flows back as labeled samples and is used to tune thresholds iteratively.

    5. How Do You Generate Visualizations and Custom Dashboards?

    Chart data and chart rendering must be kept separate: the model outputs only aggregated data and chart type suggestions, while rendering is done deterministically by the frontend. Dashboard configuration should be persisted in a structure like this:

    1. {
    2. "dashboard_id": "db_01",
    3. "time_window": {"type": "rolling", "value": "last_90_days"},
    4. "group_by": ["category", "merchant_ref"],
    5. "account_scope": ["acc_****1234"],
    6. "charts": [{"type": "stacked_bar", "metric": "spend_by_category"}],
    7. "exclude_categories": ["transfer_internal"]
    8. }

    This way, users can reproduce their dashboard with one click the next time they log in, and the model does not have to re-infer parameters on every render.

    6. How Should You Explain Financial Concepts Correctly?

    Interest-related questions are the easiest to get wrong, so three concepts must be distinguished first: annual interest rate / APR (an annualized rate that includes fees; installment scenarios often use a quoted “fee rate” that is converted to an annual basis, and a nominal rate understates the true cost), daily interest rate, and interest already accrued.

    Credit card revolving interest is typically calculated daily, based on the average daily balance. It is also affected by the grace period and how the user repays: paying in full and on time usually incurs no interest, whereas cash advances or failing to pay in full trigger interest from the posting date.

    The correct formula: interest ≈ average daily balance × daily rate × number of days, where daily rate = annual rate / 365.

    Example: billing cycle of 30 days, average daily revolving balance of ¥8,000, annual rate of 18.25%.

    • Daily rate: 18.25% / 365 = 0.05%
    • Interest: 8,000 × 0.05% × 30 = ¥120
    • A wrong approach, such as “annual rate × amount spent,” would give 8,000 × 18.25% = ¥1,460. That figure covers a full year and overstates the result by roughly 12 times.

    The answer should follow this order: “first state the assumptions (how interest is calculated, whether the grace period has already been lost, which average daily balance was used), then give the result.” This lets users judge whether the conclusion is credible. If the bank’s interest calculation rules cannot be determined, label the answer as an estimate and provide an entry point for verification.

    7. How Should You Isolate Privacy and Data?

    1. Context isolation: conversations in a dedicated financial feature are not used for model training. Outside that feature, the user’s existing privacy settings still apply, which prevents sensitive financial conversations from contaminating general-purpose data.
    2. Minimize what enters the model context: only aggregated or redacted results and the necessary fields may enter prompts. Full transaction records stay in an encrypted data warehouse or object storage, with field-level encryption, keys managed by a KMS, partitioning by account, and access controlled by IAM authentication and logged for audit. The model side receives only Top N aggregated results and category labels.
    3. Redact merchant names: a merchant name is personal information that can identify a specific merchant and spending context (it can reveal where someone lives, medical visits, religious affiliation, and so on). The plaintext merchant_name is stored locally only. Before entering the model, it is replaced with a merchant_ref (e.g., m_7f3a) or a normalized category (“Dining - Coffee”), keeping only the granularity needed to answer the question.
    4. Unlinking at any time: once a user unlinks, immediately stop pulling data, revoke the upstream authorization, and delete cached and detailed data according to the retention policy.
    5. No commercial steering: no embedded ads, no paid recommendations, and no pitching of credit or wealth management products, so that conclusions stay neutral.

    Regulatory check (legal review required): privacy and open banking regulations for the markets you operate in. These include, for example, the GDPR and PSD2, and PIPL (sensitive personal information, separate consent, data minimization) and the Data Security Law and its supporting standards where those apply. Cross-border scenarios also require an assessment of data export requirements.

    Common mistakes: putting full statements directly into the context; failing to stop upstream pulls after unlinking; and letting plaintext merchant names end up in logs.

    8. Pre-Launch Checklist

    Check item Acceptance criteria Owner
    Permission checks No write APIs; read-only token scopes minimized and covered by tests Backend / Security
    Credential boundary Aggregation provider’s compliance credentials and retention policies evaluated and documented Risk / Legal
    Token lifecycle Refresh, rotation, and revocation propagation covered by automated tests; zero pulls after unlinking Backend
    Query whitelist 100% of non-whitelisted metrics rejected; no code path that executes SQL Backend
    Parameter clarification When time or account information is missing, or a relative time cannot be anchored, the assistant asks back instead of guessing Algorithms / Product
    Redaction rules 100% of free-text fields such as merchant names redacted before entering the model; sampled audits Algorithms / Security
    Error messaging Key figures labeled “treat bank records as authoritative” with an entry point for verification Product / Frontend
    Anomaly thresholds False positive rate < 5%; feedback entry point and labeled-sample feedback loop in place Algorithms
    Interest calculation basis Uses average daily balance × daily rate × days; covered by unit tests Backend / Finance
    Unlinking and deletion Detailed data deletion, cache cleanup, and upstream revocation are all traceable (three steps) Backend / Compliance
    Audit and alerting Token usage and anomalous access patterns logged and alerted on Ops / Security
    Regulatory check Legal sign-off completed before launch Legal

    On the deployment side, data ingestion, offline computation, dashboard rendering, logging, and monitoring typically rely on general-purpose components from established public clouds. These include object storage, message queues, container orchestration, and log and monitoring/alerting services, which avoids rebuilding these capabilities in-house.

    Summary

    The positioning of this kind of assistant can be compressed into one sentence: use read-only permissions as the baseline, connect to account data securely, have the LLM handle intent understanding and query orchestration, and have the deterministic computation layer handle precise data retrieval and attribution. Then layer on spending insights, visualization, and explainability, and guard the sensitive-data boundary with context minimization, redaction, auditing, and revocable unlinking. Holding on to “analyze without operating, understand without overstepping, and retrieve data without letting the model do the money math” is what makes it possible to build a genuinely useful product that stays compliant and secure.