Fortuna is a personal finance system that tracks a person's whole financial life — bank accounts, credit cards, investments, expenses and earnings — in one place, and turns that history into tabular views, drillable charts and forward projections. This repository is the back end only: an HTTP API that owns the domain, the data and the integrations. The Flutter application that runs on Windows, Linux, the browser and mobile is a separate repository and a separate consumer of this API.
Status: version 1.0.0 is released, and every issue in the backlog below is closed.
- Tracks bank accounts, credit cards and investments, each with its own currency and history.
- Records expenses, earnings and transfers, plus installment purchases and recurring commitments.
- Ingests from several sources — manual entry, the Pluggy open-banking API, Excel spreadsheets and supported PDF statement layouts — behind one pluggable source contract.
- Handles multiple currencies with exact decimal arithmetic and dated, attributable exchange rates from the Banco Central do Brasil's PTAX service.
- Serves the data as a spreadsheet-shaped table and as chart aggregations that drill down to the individual transactions behind any figure.
- Projects the future from recurring commitments, unbilled installments and statements due.
- Exports to CSV, Excel and PDF, and files attachments against transactions.
- Runs offline on a desktop, or multi-user on a shared instance, with the same client-facing contracts across its native and managed cores.
- Move money. No payments, no transfers executed at a bank, no trading. Fortuna reads and records; it never writes to a financial institution.
- Give financial advice. A projection is arithmetic over the user's own data.
- Tax filing or accounting compliance.
- Store bank credentials. Open-banking access lives with Pluggy and is referenced by token.
- Manage users. Identity belongs to the Heimdall API; the desktop offline account is the one exception, and Fortuna owns it because there is no network to reach Heimdall over.
- Render a user interface. The API serves the numbers; the Flutter client draws them.
The project is specified before it is built. Start with the initial/ documents for context, then
the requirements/ documents for the normative detail.
| Document | What's in it |
|---|---|
| Brainstorm | The original free-form notes this project grew from. |
| Project Overview | What the project is, who it's for, and how success is measured. |
| Technology Stack | The informal stack decisions. |
| Workflow | How one use case is delivered, step by step. |
| Business Rules | Domain entities, relationships, and the BR-01 … BR-46 rules. |
| Vision Document | Stakeholders, positioning, and the F-01 … F-20 features. |
| System Requirements Document | The FR-<AREA>-xx and NFR-xx requirements, data model, endpoint surface, authorization matrix and traceability. |
| Use Case Specification Document | The application use cases, their flows, and their AF-xx alternatives. |
| Development Workflow Document | The normative branch pattern, issue lifecycle, approval gates, and Definition of Done. |
| Testing Specification Document | How tests are written, named, and run. |
| Technology Stack Document | The single source of truth for every technology and version. |
| Operations & Infrastructure Document | Layout, configuration, logging, health, UC-75, and the IR-01 … IR-20 platform requirements. |
Prerequisites: the .NET 10 SDK and either a reachable PostgreSQL instance for a shared deployment or local SQLite storage for desktop offline mode. Building the in-process desktop core also requires the stable Rust toolchain. Docker is required for the container workflow — see the Technology Stack Document and the Operations & Infrastructure Document.
git clone https://git.techcraftsmen.net/artur-rios/fortuna-api.gitdotnet restore src/ArturRios.Fortuna.slnConfiguration is resolved entirely from environment variables prefixed FORTUNA_. Copy the example
file for your environment from docker/ and fill it in; no secret is ever committed.
cp docker/local.env.example docker/local.envOne compose file serves all four environments, differing only in the environment file supplied
(docker/<environment>.env.example). example.com stands for the real domain:
| Environment | Where | Deployed by | ASPNETCORE_ENVIRONMENT |
Address | Database |
|---|---|---|---|---|---|
local |
The developer's Windows machine, Docker Desktop | By hand, with the command below, or with yggdrasil's scripts/deploy.sh local fortuna-api … |
Development |
http://localhost:8083 |
fortuna_local, on the PostgreSQL installed on Windows |
development |
The VPS, on demand (started only when used) | Jenkins, on every push to develop |
Development |
https://fortuna-api-dev.example.com |
fortuna_development, on the VPS's PostgreSQL |
homologation |
The VPS, on demand | Jenkins, on every push of a release/x.y.z branch |
Production |
https://fortuna-api-hml.example.com |
fortuna_homologation |
production |
The VPS, always on | Jenkins, on a green release/x.y.z → main pull request |
Production |
https://fortuna-api.example.com |
fortuna |
Locally:
docker compose --env-file docker/local.env up -d --buildLocal signs in with Fortuna's own local account (FORTUNA_LOCAL_AUTH_ENABLED=true). To sign in
through the local heimdall-api instead, it is http://host.docker.internal:8080/: plain HTTP is
accepted because that host resolves to a private address. The API has no CORS support, so a
browser build of the web UI on another local port cannot call it; the UI's README describes how to
work with it locally.
The three VPS environments are deployed by yggdrasil,
from a copy of the matching template at /etc/yggdrasil/<environment>/fortuna-api.env on the VPS:
Traefik serves the API on its own host and under the web UI's host at /api/, so the browser
reaches it on the UI's own origin. Homologation runs as Production because the API treats every
other ASP.NET environment as a debugging one (Swagger UI, detailed errors). Each of them has its
own secrets, its own Heimdall (FORTUNA_HEIMDALL_BASE_URL, which is HTTPS, or plain HTTP to a
private address: inside yggdrasil http://heimdall-api.<environment>:8080/) whose public signing
keys it reads from FORTUNA_HEIMDALL_JWKS_URL, and its own FORTUNA_LOCAL_TOKEN_SECRET for
local-account tokens. Only those published keys verify a Heimdall token: the shared secret Fortuna
once held for Heimdall's HS256 tokens (FORTUNA_AUTH_TOKEN_SECRET, _PREVIOUS) is no longer read
since 4.0.0, and a start-up warning names it while it is still set. Under yggdrasil that warning
never appears, because the compose file no longer passes either variable to the container, so
check the store with ygg vars list <scope> --keys instead. The release process, and how to turn
development or homologation on, are in CONTRIBUTING.md.
The API's liveness is observable at the public GET /healthcheck endpoint. Instance administrators
can inspect dependency health at GET /healthcheck/detailed.
Prometheus metrics are served at GET /metrics only on the private port 9464
(FORTUNA_METRICS_PORT; 0 disables them), never through the public port 8080. The compose file
does not publish that port: Prometheus scrapes the container over a private Docker network — see the
Operations & Infrastructure Document.
Logs are structured JSON, written to the console and to daily files under
FORTUNA_LOG_DIRECTORY/<yyyy>/<MM>/. Every API request is logged with its client IP address,
which is personal data under the GDPR and the LGPD, and the address is tagged on the request's trace
as client.address. Behind a reverse proxy, list the proxy in FORTUNA_FORWARDED_KNOWN_NETWORKS or
FORTUNA_FORWARDED_KNOWN_PROXIES so the logs and the rate limiter see the real caller rather than the
proxy — see the
Operations & Infrastructure Document.
For desktop offline mode, keep the same application configuration and select the local provider:
FORTUNA_DATA_DATABASETYPE=SQLite
FORTUNA_DATA_CONNECTIONSTRING=/absolute/path/to/fortuna.db
FORTUNA_RUN_MIGRATIONS=truePostgreSQL remains the server default. Each provider has its own EF migration history while sharing the domain model; changing providers requires no application-code change.
The desktop client can instead embed the native core without starting the HTTP host or opening a
listening socket. It ships fortuna_core.dll on Windows or libfortuna_core.so on Linux together
with the generated C header. It has 115 generated offline route exports. Capability discovery
lists the ones the native core implements, the ones it exports but does not implement yet (file
imports, data-set exports, reports, projections, transfers, installment plans, statements and a few
computed views, which answer 501), and the network-dependent routes that remain HTTP-only. The
native core owns its Rust domain/persistence implementation and SQLite schema; see
native/README.md for the ABI, ownership and build contract.
Seven milestones, in dependency order. Every milestone after M-01 depends on it.
Closed counts are as of the 1.0.0 release — the milestones page
and the project board are the live view. The board's
Status field carries the lifecycle the
Development Workflow Document defines:
Todo → In Progress → Testing → Done.
| Milestone | Delivers | Depends on | Issues | Status |
|---|---|---|---|---|
| M-01 — Foundation | The project scaffold, data layer, job runner and CI every use case is built on | — | 6 | 6 / 6 closed |
| M-02 — Access and cross-cutting mechanisms | Token validation, profile provisioning, the desktop local account, currencies and exchange rates, lifecycle, account erasure and the audit trail | M-01 | 16 | 16 / 16 closed |
| M-03 — Holdings | Financial accounts, credit cards with billing cycles and statements, and investments | M-02 | 20 | 20 / 20 closed |
| M-04 — Money movement | Transactions, transfers, installment plans, recurring commitments and reconciliation | M-03 | 12 | 12 / 12 closed |
| M-05 — Organization and planning | Categories, tags, counterparties, budgets and goals | M-04 | 11 | 11 / 11 closed |
| M-06 — Ingestion | The source contract, Pluggy connections and synchronization, Excel and Nubank PDF imports, and job monitoring | M-04 | 11 | 11 / 11 closed |
| M-07 — Insight and output | Attachments, tables, chart aggregations with drill-down, net position, projections, export and the health check | M-05, M-06 | 15 | 15 / 15 closed |
The backlog tracks one branch and one pull request per issue. Use cases retain the stricter invariant: one use case = one branch = one issue = one pull request.
| Issue | Work | Spec |
|---|---|---|
| #1 | ✅ Project scaffold and initial infrastructure | Operations & Infrastructure |
| #155 | ✅ Support SQLite as the persistence provider for desktop offline mode | Operations & Infrastructure |
| #156 | ✅ Expose the Fortuna core over a C ABI for in-process desktop use | Operations & Infrastructure |
| #157 | ✅ Export the offline operation surface over the C ABI | Operations & Infrastructure |
| #162 | ✅ Publish exact decimal values as JSON strings | Technology Stack |
| #163 | ✅ Publish enum member names in OpenAPI | System Requirements |
| Issue | Work | Spec |
|---|---|---|
| #2 | ✅ UC-01: Authenticate a Request with a Heimdall Token | Use Case Specification |
| #3 | ✅ UC-02: Provision a User Profile on First Access | Use Case Specification |
| #4 | ✅ UC-03: Create a Desktop Local Account | Use Case Specification |
| #5 | ✅ UC-04: Authenticate with a Local Account | Use Case Specification |
| #6 | ✅ UC-05: Recover a Local Account with a Recovery Code | Use Case Specification |
| #7 | ✅ UC-06: Regenerate Local Account Recovery Codes | Use Case Specification |
| #8 | ✅ UC-07: List Supported Currencies | Use Case Specification |
| #9 | ✅ UC-08: Synchronize Exchange Rates | Use Case Specification |
| #10 | ✅ UC-09: Record a Manual Exchange Rate | Use Case Specification |
| #11 | ✅ UC-10: View Figures in a Display Currency | Use Case Specification |
| #53 | ✅ UC-52: Delete and Restore a Record | Use Case Specification |
| #54 | ✅ UC-53: Read the Audit Trail | Use Case Specification |
| #153 | ✅ UC-76: Authenticate through the Fortuna API | Use Case Specification |
| #154 | ✅ UC-77: Manage Credentials and Two-Factor Authentication through the Fortuna API | Use Case Specification |
| #158 | ✅ UC-78: Erase a User Account | Use Case Specification |
| #175 | ✅ Resend an emailed second-factor code for an outstanding challenge | Use Case Specification |
| #208 | ✅ UC-81: Register through the Fortuna API | Use Case Specification |
| Issue | Work | Spec |
|---|---|---|
| #12 | ✅ UC-11: Create a Financial Account | Use Case Specification |
| #13 | ✅ UC-12: View Financial Accounts | Use Case Specification |
| #14 | ✅ UC-13: Update a Financial Account | Use Case Specification |
| #15 | ✅ UC-14: View an Account Balance | Use Case Specification |
| #16 | ✅ UC-15: Delete a Financial Account | Use Case Specification |
| #17 | ✅ UC-16: Create a Credit Card | Use Case Specification |
| #18 | ✅ UC-17: View Credit Cards and Limits | Use Case Specification |
| #19 | ✅ UC-18: Update a Credit Card | Use Case Specification |
| #20 | ✅ UC-19: Delete a Credit Card | Use Case Specification |
| #21 | ✅ UC-20: Assign a Charge to a Billing Cycle | Use Case Specification |
| #22 | ✅ UC-21: Close a Statement | Use Case Specification |
| #23 | ✅ UC-22: View a Statement | Use Case Specification |
| #24 | ✅ UC-23: Settle a Statement | Use Case Specification |
| #25 | ✅ UC-24: Create an Investment | Use Case Specification |
| #26 | ✅ UC-25: Record an Investment Movement | Use Case Specification |
| #27 | ✅ UC-26: Record an Investment Valuation | Use Case Specification |
| #28 | ✅ UC-27: View Investments and Positions | Use Case Specification |
| #29 | ✅ UC-28: Update an Investment | Use Case Specification |
| #30 | ✅ UC-29: Delete an Investment | Use Case Specification |
| #178 | ✅ Accept IncludeDeleted on the credit-card and investment list endpoints | System Requirements |
| Issue | Work | Spec |
|---|---|---|
| #31 | ✅ UC-30: Record a Transaction | Use Case Specification |
| #32 | ✅ UC-31: Search Transactions | Use Case Specification |
| #33 | ✅ UC-32: Update a Transaction | Use Case Specification |
| #34 | ✅ UC-33: Delete a Transaction | Use Case Specification |
| #35 | ✅ UC-34: Record a Transfer | Use Case Specification |
| #36 | ✅ UC-35: Delete a Transfer | Use Case Specification |
| #37 | ✅ UC-36: Record an Installment Purchase | Use Case Specification |
| #38 | ✅ UC-37: Define a Recurring Transaction | Use Case Specification |
| #39 | ✅ UC-38: Materialize Recurring Occurrences | Use Case Specification |
| #40 | ✅ UC-39: Update a Recurring Transaction | Use Case Specification |
| #41 | ✅ UC-40: Reconcile a Transaction | Use Case Specification |
| #176 | ✅ List a user's recurring transactions | System Requirements |
| Issue | Work | Spec |
|---|---|---|
| #42 | ✅ UC-41: Create a Category | Use Case Specification |
| #43 | ✅ UC-42: View the Category Tree | Use Case Specification |
| #44 | ✅ UC-43: Update a Category | Use Case Specification |
| #45 | ✅ UC-44: Reassign Transactions Between Categories | Use Case Specification |
| #46 | ✅ UC-45: Delete a Category | Use Case Specification |
| #47 | ✅ UC-46: Manage Tags | Use Case Specification |
| #48 | ✅ UC-47: Manage Counterparties | Use Case Specification |
| #49 | ✅ UC-48: Define a Budget | Use Case Specification |
| #50 | ✅ UC-49: Track Budget Consumption | Use Case Specification |
| #51 | ✅ UC-50: Define a Goal | Use Case Specification |
| #52 | ✅ UC-51: Track Goal Progress | Use Case Specification |
| Issue | Work | Spec |
|---|---|---|
| #55 | ✅ UC-54: Discover Available Data Sources | Use Case Specification |
| #56 | ✅ UC-55: Connect an Institution through Pluggy | Use Case Specification |
| #57 | ✅ UC-56: Synchronize from a Connection | Use Case Specification |
| #58 | ✅ UC-57: Reauthenticate a Connection | Use Case Specification |
| #59 | ✅ UC-58: Revoke a Connection | Use Case Specification |
| #60 | ✅ UC-59: Import Transactions from an Excel Workbook | Use Case Specification |
| #61 | ✅ UC-60: Import a Nubank Credit Card Invoice PDF | Use Case Specification |
| #62 | ✅ UC-61: Monitor an Import Job | Use Case Specification |
| #63 | ✅ UC-62: Retry a Failed Import Job | Use Case Specification |
| #64 | ✅ UC-63: Review Imported Records | Use Case Specification |
| #160 | ✅ UC-80: Record Processing Consent | Use Case Specification |
| Issue | Work | Spec |
|---|---|---|
| #65 | ✅ UC-64: Attach a Document to a Transaction | Use Case Specification |
| #66 | ✅ UC-65: Download an Attachment | Use Case Specification |
| #67 | ✅ UC-66: Delete an Attachment | Use Case Specification |
| #68 | ✅ UC-67: Query Records as a Table | Use Case Specification |
| #69 | ✅ UC-68: Aggregate Transactions for a Chart | Use Case Specification |
| #70 | ✅ UC-69: Drill Into an Aggregation | Use Case Specification |
| #71 | ✅ UC-70: View the Net Position | Use Case Specification |
| #72 | ✅ UC-71: Project Cash Flow | Use Case Specification |
| #73 | ✅ UC-72: View Committed Obligations | Use Case Specification |
| #74 | ✅ UC-73: Export a Data Set | Use Case Specification |
| #75 | ✅ UC-74: Retrieve a Completed Export | Use Case Specification |
| #76 | ✅ UC-75: Check API Health | Operations & Infrastructure |
| #159 | ✅ UC-79: Export All Personal Data | Use Case Specification |
| #161 | ✅ Report API contract version from liveness | Operations & Infrastructure |
| #177 | ✅ List a transaction's attachments | System Requirements |
Notable changes in each release are recorded in CHANGELOG.md.
Building from source, running the tests, the delivery workflow, the branching model and the release process are described in CONTRIBUTING.md.
The GitHub repository is a read-only mirror for the real, open-source code of fortuna-api. Its home is Forgejo, at https://git.techcraftsmen.net/artur-rios/fortuna-api. If you want to contribute, e-mail me at arturdev@duck.com.
This project is licensed under the Artur Rios Attribution License. It is free and open source: you may view, use, fork, modify, and extend it, provided you credit the author, Artur Rios, with a link to artur-rios.tech or github.com/artur-rios.
Copyright (c) 2026 Artur Rios.