{"id":49851032,"url":"https://github.com/maciejb2k/open-banking-rails","last_synced_at":"2026-05-14T16:08:32.074Z","repository":{"id":355227950,"uuid":"1222859235","full_name":"maciejb2k/open-banking-rails","owner":"maciejb2k","description":"Self-hosted open-banking aggregator for the EU. Rails 8 + PSD2/AISP (Enable Banking) + AI transaction categorization. MIT.","archived":false,"fork":false,"pushed_at":"2026-05-02T14:44:37.000Z","size":1971,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-02T15:29:27.601Z","etag":null,"topics":["ai","aisp","banking","budgeting","enable-banking","expense-tracker","finance-tracker","fintech","hotwire","llm","open-banking","personal-finance","postgresql","psd2","rails","ruby-on-rails","self-hosted","sidekiq","transaction-categorization"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maciejb2k.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-27T19:25:15.000Z","updated_at":"2026-05-02T14:44:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/maciejb2k/open-banking-rails","commit_stats":null,"previous_names":["maciejb2k/open-banking-rails"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/maciejb2k/open-banking-rails","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejb2k%2Fopen-banking-rails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejb2k%2Fopen-banking-rails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejb2k%2Fopen-banking-rails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejb2k%2Fopen-banking-rails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maciejb2k","download_url":"https://codeload.github.com/maciejb2k/open-banking-rails/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maciejb2k%2Fopen-banking-rails/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33032638,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","aisp","banking","budgeting","enable-banking","expense-tracker","finance-tracker","fintech","hotwire","llm","open-banking","personal-finance","postgresql","psd2","rails","ruby-on-rails","self-hosted","sidekiq","transaction-categorization"],"created_at":"2026-05-14T16:08:28.774Z","updated_at":"2026-05-14T16:08:32.067Z","avatar_url":"https://github.com/maciejb2k.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Open Banking Rails\n\n\u003cp\u003e\n  \u003cimg src=\"https://img.shields.io/badge/ruby-%23CC342D.svg?style=for-the-badge\u0026logo=ruby\u0026logoColor=white\" alt=\"Ruby\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/rails_8-%23CC0000.svg?style=for-the-badge\u0026logo=ruby-on-rails\u0026logoColor=white\" alt=\"Rails 8\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/hotwire-%234c4c4c.svg?style=for-the-badge\u0026logo=hotwire\u0026logoColor=white\" alt=\"Hotwire\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/postgresql_17-%23316192.svg?style=for-the-badge\u0026logo=postgresql\u0026logoColor=white\" alt=\"PostgreSQL 17\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/sidekiq-%23B1003E.svg?style=for-the-badge\u0026logo=sidekiq\u0026logoColor=white\" alt=\"Sidekiq\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/opentelemetry-%23425CC7.svg?style=for-the-badge\u0026logo=opentelemetry\u0026logoColor=white\" alt=\"OpenTelemetry\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge\u0026logo=docker\u0026logoColor=white\" alt=\"Docker\"\u003e\n\u003c/p\u003e\n\n### Automatic personal finance tracking and analysis\n\nOpen Banking aggregator with PSD2-compliant multi-bank sync via Enable\nBanking and a hybrid rules + LLM categorization engine. Transactions\narrive and sync on their own. Merchants and categories are inferred, not typed.\n\n![Analytics](docs/screenshots/analytics.png)\n\n## What you get\n\n| Feature                   | What it does                                                                                               |\n|---------------------------|------------------------------------------------------------------------------------------------------------|\n| 🏦 **Any EU bank**        | Personal accounts via Enable Banking (AISP). One-time PSD2 consent, no screen scraping.                    |\n| 🔄 **Syncs itself**       | New transactions show up automatically. No CSV imports, no manual refresh.                                 |\n| 🏷️ **Categorizes itself** | Rules cover the obvious cases, an LLM does the rest. Monthly AI summary, with every number verified.       |\n| 💶 **Tracks cash too**    | Manual entries for what your bank doesn't see. Same categories and dashboard as bank transactions.         |\n| 🏠 **Self-hosted**        | Runs on your own server, VPS or VPN. No SaaS account, full observability built in.                         |\n| 🔒 **Encrypted at rest**  | Sensitive columns encrypted in the database. LLM only sees normalized titles and counterparties.           |\n| 🕶️ **Privacy filter**     | One toggle blurs amounts, IBANs and names on the UI. Made for safe screen-shares and screenshots.          |\n| 📜 **Open source**        | MIT licensed. Fork it, modify it, deploy it yourself.                                                      |\n\n![Dashboard](docs/screenshots/transactions.png)\n\n## Why Enable Banking\n\nYou can't just plug into a bank's API on your own. PSD2 requires you to\nbe a licensed AISP, and getting that license is expensive and out of\nreach for a personal project. \n\n[Enable Banking](https://enablebanking.com/docs/api/reference/)\nis a licensed provider that sits in the middle: they offer one API for\n[most European banks](https://enablebanking.com/open-banking-apis), and\ntheir free tier covers personal use. GoCardless used to offer a similar\nfree tier but dropped it for personal projects, which is why Enable\nBanking is the current choice.\n\nThe app talks to the provider through an adapter, so swapping in a different one later is a small change, not a rewrite.\n\n### Read-only by design\n\nAISP is the read-only PSD2 role. It covers account info and transaction history, nothing else. Initiating payments is a separate role (PISP) with a separate license, a separate consent flow, and credentials this app does not hold and never asks for. Even if the API keys leaked, the worst case is someone reading your transactions. Moving money is structurally impossible here.\n\n## Tech stack\n\n| Layer            | Choice                                                        |\n| ---------------- | ------------------------------------------------------------- |\n| Web              | Rails 8.1, Hotwire (Turbo + Stimulus), Tailwind, Propshaft, Importmap |\n| Realtime         | Turbo Streams over Action Cable (Redis-backed) for live sync + LLM enrichment progress |\n| Database         | PostgreSQL 17, `ltree` + GiST for hierarchical categories, `scenic` for versioned views, `paper_trail` for audit |\n| Background jobs  | Sidekiq + Redis 7, `sidekiq-cron` for per-connection auto-sync |\n| Auth             | Devise                                                        |\n| Open Banking     | [Enable Banking](https://enablebanking.com/) (PSD2 AISP)      |\n| LLM              | [`ruby_llm`](https://github.com/crmne/ruby_llm) (OpenAI `gpt-5-mini` default) |\n| Money            | `money-rails` → Money Archetype |\n| Search / paging  | Pagy, Ransack                                                 |\n| Observability    | OpenTelemetry SDK → Collector → Tempo / Loki / Prometheus / Grafana / Alertmanager |\n| Testing          | RSpec, FactoryBot, shoulda-matchers, SimpleCov                |\n| Security         | Brakeman, bundler-audit, ActiveRecord encryption              |\n\n## Architecture\n\n### End-to-end flow\n\nA transaction at your bank reaches your dashboard without anyone\ntouching it.\n\n```mermaid\nflowchart LR\n    Bank[\"Your bank\"] --\u003e|PSD2\u003cbr/\u003eEnable Banking| Sync[\"Background sync\"]\n    Sync --\u003e Engine[\"Rules + LLM\u003cbr/\u003ecategorization\"]\n    Engine --\u003e DB[(\"Encrypted ledger\")]\n    DB --\u003e Dash[\"Dashboard\u003cbr/\u003e+ AI summary\"]\n```\n\n### Trust boundary\n\nWhat lives on your infrastructure, and the minimum that ever leaves it.\n\n```mermaid\nflowchart LR\n    subgraph External[\"External\"]\n        Bank[\"Your bank\u003cbr/\u003evia PSD2\"]\n        LLM[\"LLM API\"]\n    end\n\n    subgraph Self[\"Your infrastructure\"]\n        App[\"App + jobs\"]\n        DB[(\"Encrypted Postgres\")]\n        UI[\"Dashboard\"]\n    end\n\n    Bank --\u003e|transactions| App\n    App --\u003e DB\n    App -.-\u003e|titles only| LLM\n    LLM -.-\u003e|categories| App\n    DB --\u003e UI\n```\n\n### Hybrid classification\n\nEach new transaction gets a merchant and a category. Rules match the\nobvious cases. The LLM picks up the long tail, gated by a confidence\nthreshold before auto-applying.\n\n```mermaid\nflowchart LR\n    TX[\"New transaction\"] --\u003e Rule{\"Rule\u003cbr/\u003ematch?\"}\n    Rule --\u003e|yes| Apply[\"Apply merchant\u003cbr/\u003e+ category\"]\n    Rule --\u003e|no| LLM[\"LLM suggester\u003cbr/\u003e(merchant + category)\"]\n    LLM --\u003e Conf{\"Confidence\u003cbr/\u003e≥ threshold?\"}\n    Conf --\u003e|yes| Apply\n    Conf --\u003e|no| Review[\"Pending review\"]\n    Apply --\u003e DB[(\"Ledger entry\")]\n```\n\n## Self-hosting\n\nRequires Docker 24+ with the Compose plugin. ~2 GB RAM, ~5 GB disk.\n\n```bash\nmkdir open-banking-rails \u0026\u0026 cd open-banking-rails\ncurl -fsSL https://raw.githubusercontent.com/maciejb2k/open-banking-rails/main/docker-compose.prod.yml -O\ndocker compose -f docker-compose.prod.yml up -d\n```\n\nOpen \u003chttp://localhost:3000\u003e → first-run page asks for email + password.\nThat's your only admin account; no second sign-up.\n\nSecrets (`SECRET_KEY_BASE`, AR encryption keys) auto-generate on first\nboot and live in a Docker volume. `down`/`up` keeps everything; `down -v`\n**wipes your data**. The `app_secrets` volume is also required to\ndecrypt your backups - back it up alongside `./backups/`.\n\n### Upgrade\n\n```bash\ndocker compose -f docker-compose.prod.yml pull\ndocker compose -f docker-compose.prod.yml up -d\n```\n\nPre-migration `pg_dump` runs automatically. Pin a specific image with\n`APP_TAG=v1.2.3` in `.env`.\n\n### Backups\n\nDaily `pg_dump` to `./backups/` with 7 daily / 4 weekly / 6 monthly\nretention.\n\n### Restore\n\n```bash\ndocker compose -f docker-compose.prod.yml stop app worker\ndocker compose -f docker-compose.prod.yml --profile restore run --rm restore\ndocker compose -f docker-compose.prod.yml up -d app worker\n```\n\nOptionally pass a specific dump path after `restore`. Without an arg, it\npicks the newest `.sql.gz` under `./backups/`.\n\n### Email (optional)\n\n```\nSMTP_ADDRESS=smtp.example.com\nSMTP_PORT=587\nSMTP_USERNAME=...\nSMTP_PASSWORD=...\n```\n\nPassword reset without SMTP:\n\n```bash\ndocker compose -f docker-compose.prod.yml exec app \\\n    bin/rails runner \"User.first.send_reset_password_instructions\"\n```\n\n## Configuring App\n\nOnce the container is up and you've created the admin account, this is the post-install walkthrough that takes you from a blank app to a working dashboard.\n\n\u003e **Heads up: you'll go through a bank consent flow twice.** Once on Enable Banking to whitelist the account, then again in this app to actually read it. The two screens look identical and people lose hours conflating them. The first consent is a throwaway whitelist step; only the second one grants transaction access.\n\n1. Register on [Enable Banking](https://enablebanking.com/) and enable MFA.\n2. Create a new **API Application** in Enable Banking. Treat it like an OAuth app. It's the gateway this app uses to talk to your bank.\n3. **Whitelist your accounts (consent #1, 1 day).** In the Enable Banking UI in the created Application, link each account through their bank flow. The page looks exactly like a real consent screen, but on the free tier this step only registers the account on Enable Banking's whitelist. It does not grant read access. The 1-day expiry is irrelevant; once an account is whitelisted it stays usable. (The free plan has no API for adding accounts, which is why this manual round-trip exists.)\n4. In this app, open **TPP Credentials**, mirror the values from the Enable Banking Application, and test the connection.\n5. **Authorize transaction reads (consent #2, 90–180 days).** Go to **Bank Connections → Add bank** and run the bank flow again. This one goes through the Enable Banking API and grants *read-only* access to transactions for 90–180 days, depending on the bank. This is the consent that matters and the one you'll renew when it expires. No payments, read-only by design. Use **Refresh from API** on the connection's show page to confirm everything is wired.\n6. Bank Accounts appear automatically once the connection syncs.\n7. Open **Sync Transactions** and pull history. Most banks cap this at the last 90 days from the day you connect.\n8. In **Preferences**, paste an LLM API key (~$5 of OpenAI credit goes a long way) and test the connection.\n9. Open **AI Enrichments** and walk through the queue until categories match how you think.\n10. Open the dashboard and you're done.\n\n## API \u0026 MCP\n\nThe dashboard is also a REST API and an MCP server. Get a token from\n**Settings → API Tokens** and point your client at `/api/v1/...` or\n`/mcp`. Swagger docs live at `/api/v1/docs`. The MCP endpoint plugs into\nClaude Desktop and any other MCP client, with tools for transactions,\ncategories, merchants and analytics.\n\n![API Tokens](docs/screenshots/api_tokens.png)\n\n![OpenAPI](docs/screenshots/openapi.png)\n\n## Styleguide \u0026 Component Library\n\nNo `ActiveAdmin`, no `Avo`. The admin panel is hand-rolled on Tailwind\nand shipped fast with Claude Code, following project rules\n(`AGENTS.md`) that pin the LLM to design tokens, sensitive-data\nwrapping and reusable partials. The output is a small component library\nand a fully responsive admin built on top of it.\n\nBrowse `/admin/styleguide` for every component and its variants.\n\n![Styleguide](docs/screenshots/styleguide.png)\n\n## Screenshots\n\n![Login](docs/screenshots/login.png)\n\n![AI Enrichements](docs/screenshots/ai_enrichements.png)\n\n![Sync](docs/screenshots/sync.png)\n\n![Matching Engine](docs/screenshots/matching_engine.png)\n\n![Preferences](docs/screenshots/preferences.png)\n\n![Import/Export](docs/screenshots/importer.png)\n\n## License\n\nMIT. See [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaciejb2k%2Fopen-banking-rails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaciejb2k%2Fopen-banking-rails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaciejb2k%2Fopen-banking-rails/lists"}