- Python 51.7%
- JavaScript 22.7%
- CSS 13.1%
- HTML 10.8%
- Shell 1.7%
Neither / nor /static/* carried a Cache-Control header, so browsers fell back to heuristic caching - roughly 10% of the file's age. An index.html that had been deployed for days was therefore held for hours without revalidating, and an updated app kept showing the previous UI. The asset URLs made it worse: ?v=2.3.4 was hard-coded, so every frontend change since 2.3.4 shipped under the same cache key. - index.html now uses ?v=__ASSET_VERSION__, substituted at serve time with the installed version, so asset URLs change whenever the app does - the HTML shell is served with Cache-Control: no-store; the assets it points at stay cacheable because their URLs now carry the version Also gives the settings panel a viewport-bounded max-height and its own scrollbar. It is position:fixed, so page scrolling cannot reach anything that overflows: below roughly 768px of viewport the last sections were unreachable. Measured at 1440x900 the panel is 686px and fits, so this was not the cause of the report above - it is a latent bug on shorter screens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| backend | ||
| cli | ||
| completions | ||
| frontend | ||
| mcp_server | ||
| .gitignore | ||
| .mcp.env.example | ||
| .mcp.json | ||
| ChangeLog | ||
| cli.py | ||
| CLI_README.md | ||
| install.sh | ||
| README.md | ||
| requirements.txt | ||
| V2.7.0 | ||
| wis.aleksi.systems.har | ||
What I Spend
A modern, self-hosted personal expense tracking application with a beautiful interface, powerful analytics, and multi-user data sharing capabilities.
Table of Contents
- Features
- Screenshots
- Quick Start
- Installation
- Configuration
- Usage
- Account
- Multi-User Data Sharing
- API Reference
- CLI (Command Line Interface)
- Updating
- Claude Access Link
- MCP Server (AI Analysis)
- Project Structure
- Security
- Data Management
- Troubleshooting
- Development
Features
💰 Expense Management
- Add, edit, and delete expenses with descriptions
- Categorize expenses with nested subcategories
- Track payment methods (Cash, Card, Transfer, etc.)
- Filter by month and export to CSV or Excel (XLSX)
- Category is required for expenses; subcategory remains optional
📊 Analytics Dashboard
- Total spent for the month
- Transaction count and average per transaction
- Highest expense tracking
- Daily evolution chart (line graph)
- Category distribution (pie chart)
- Weekly comparison (bar chart)
- Payment method breakdown (horizontal bar chart)
- Top 5 expenses list
🔗 Multi-User Data Sharing
- Link user accounts without merging them
- Share expenses, categories, and payment methods between linked accounts
- Each account remains independent with its own credentials
- Link and unlink accounts at any time
- Perfect for couples or roommates tracking shared expenses
🌍 Geolocation
- Automatic GPS capture for expense locations
- Suggestions based on frequently visited places
- Smart location-based expense tracking
🌙 Dark Mode
- Full dark mode support with toggle in settings
- Persistent theme preference across sessions
🔒 Security
- Argon2id password hashing
- Session-based authentication
- Rate limiting (configurable, default 50 req/s)
- Optional French IP filtering (GeoIP)
- API key protection for all endpoints
🔄 Automatic Updates
In-App Update System
- One-click update from Settings menu
- No CLI required - updates directly from the web interface
- Automatic backup before any changes
- Real-time progress visualization
- Automatic rollback on failure
- Server restart after successful update
🎨 Modern UI
- Clean, professional design with Indigo/Violet theme
- Fully responsive (mobile, tablet, desktop)
- Interactive charts powered by Chart.js
- Toast notifications and modal dialogs
- Smooth animations and transitions
- Interactive setup wizard for first-time users
Quick Start
# Clone or navigate to the project
cd WhatISpend
# Run the automated installation
sudo ./install.sh
The installation script will:
- Create a Python virtual environment
- Install all dependencies
- Generate an API key
- Initialize the database
- Start the server with a setup wizard
Access the application: http://localhost:3000
Installation
Prerequisites
- Python 3.10+
- pip (Python package manager)
- Linux/macOS (Ubuntu/Debian recommended)
Manual Installation
-
Create virtual environment
python3 -m venv venv source venv/bin/activate -
Install dependencies
pip install -r requirements.txt -
Generate API key
python3 -c "import secrets; print(secrets.token_hex(16))" -
Configure the application
Create a
config.envfile at the project root:# Expense Tracker Configuration # API Keys (generate with the command above) API_KEY=your_api_key_here API_KEY_SALT=your_salt_secret_here # Server Configuration HOST=0.0.0.0 PORT=3000 # Session Configuration (in hours) SESSION_DURATION=24 # Rate Limiting (requests per second) RATE_LIMIT=50 # GeoIP (optional) GEOIP_ENABLED=false GEOIP_LICENSE_KEY= -
Initialize data directory
mkdir -p data echo '{}' > data/users.json echo '{}' > data/expenses.json echo '{}' > data/categories.json echo '{}' > data/payment_methods.json -
Create an admin user
python cli.py user create --username admin --password your_secure_password -
Start the server
uvicorn backend.main:app --host 0.0.0.0 --port 3000 -
Access the application
- Open your browser to:
http://localhost:3000 - Login with your credentials
- Open your browser to:
Configuration
Edit config.env to customize your installation:
| Variable | Description | Default | Required |
|---|---|---|---|
API_KEY |
API authentication key | (generated) | ✅ |
API_KEY_SALT |
Salt for user API keys | (generated) | ✅ |
HOST |
Server bind address | 0.0.0.0 |
✅ |
PORT |
Server port | 3000 |
✅ |
SESSION_DURATION |
Session lifetime (hours) | 24 |
✅ |
RATE_LIMIT |
Requests per second | 50 |
✅ |
GEOIP_ENABLED |
Enable French IP filtering | false |
❌ |
GEOIP_LICENSE_KEY |
MaxMind GeoIP license | - | ❌ |
Usage
First Login
- Navigate to
http://localhost:3000 - Enter your username and password
- You'll be redirected to the Dashboard
Dashboard
The main dashboard displays:
- Key Metrics: Total spent, transaction count, average, highest expense
- Charts: Daily evolution, category distribution, weekly comparison, payment methods
- Top Expenses: Your 5 largest expenses for the month
Managing Expenses
- Go to the Expenses page
- Click Add Expense to create a new entry
- Fill in amount, date, category, payment method, and optional description
- Optionally add GPS coordinates for location tracking
- Edit or delete existing expenses as needed
- Use the month filter to view different periods
- Export to CSV or Excel for external analysis
Managing Categories
- Go to the Categories page
- Create parent categories and subcategories
- Edit or delete categories (deletion requires no associated expenses)
Managing Payment Methods
- Go to the Payment Methods page
- Create custom payment methods (Cash, Card, etc.)
- Edit or delete as needed
Account
Your username and password are changed from Settings → Account in the web app. Both ask for your current password first.
Changing either one rotates your API key, so other signed-in devices are
signed out and have to sign in again — the browser you made the change from
stays signed in. If you use the MCP server, update .mcp.env to match.
Renaming moves everything you own with you: expenses, categories, payment methods, locations, preferences, account links and the audit trail. A username already in use is rejected.
The CLI equivalent remains available on the server:
python cli.py user reset-password -u your_username
Multi-User Data Sharing
What I Spend supports linking user accounts to share data while keeping accounts independent.
Linking Users
# Link two user accounts
python cli.py user link --user1 username1 --user2 username2
# Unlink accounts
python cli.py user unlink --user1 username1 --user2 username2
How It Works
- Shared Data: Expenses, categories, and payment methods are shared between linked accounts
- Independent Accounts: Each user maintains their own login credentials and preferences
- Flexible: Link and unlink accounts at any time without data loss
- Storage: Links are stored in
data/user_links.json
Use Cases
- Couples: Track shared household expenses while maintaining individual accounts
- Roommates: Share rent and utility expenses without merging accounts
- Family: Parents and children can share certain expense categories
API Reference
All API endpoints require the API key as a query parameter: ?key=YOUR_API_KEY
Authentication
| Endpoint | Method | Description |
|---|---|---|
/sysapi/login |
POST | Login (form: username, password) |
/sysapi/logout |
POST | Logout |
/sysapi/session |
GET | Check session validity |
Expenses
| Endpoint | Method | Description |
|---|---|---|
/sysapi/expenses |
GET | List expenses (?month=YYYY-MM) |
/sysapi/expense_create |
POST | Create expense |
/sysapi/expense_update |
POST | Update expense |
/sysapi/expense_delete |
POST | Delete expense |
Categories
| Endpoint | Method | Description |
|---|---|---|
/sysapi/categories |
GET | List categories (nested) |
/sysapi/categories_flat |
GET | List all categories (flat) |
/sysapi/category_create |
POST | Create category |
/sysapi/category_update |
POST | Update category |
/sysapi/category_delete |
POST | Delete category |
Payment Methods
| Endpoint | Method | Description |
|---|---|---|
/sysapi/payment_methods |
GET | List payment methods |
/sysapi/payment_method_create |
POST | Create payment method |
/sysapi/payment_method_update |
POST | Update payment method |
/sysapi/payment_method_delete |
POST | Delete payment method |
Statistics
| Endpoint | Method | Description |
|---|---|---|
/sysapi/dashboard-data |
GET | All dashboard statistics in one call |
/sysapi/stats |
GET | Monthly statistics |
/sysapi/daily_evolution |
GET | Daily evolution data |
/sysapi/category_distribution |
GET | Category distribution |
/sysapi/weekly_comparison |
GET | Weekly comparison |
/sysapi/payment_method_stats |
GET | Payment method statistics |
/sysapi/top_expenses |
GET | Top expenses (?limit=5) |
/sysapi/export_csv |
GET | Export expenses as CSV |
User Management
| Endpoint | Method | Description |
|---|---|---|
/sysapi/users |
GET | List all users |
/sysapi/users/link |
POST | Link two user accounts |
/sysapi/users/unlink |
POST | Unlink two user accounts |
/sysapi/users/linked |
GET | Get linked users for current user |
Share Link
| Endpoint | Method | Description |
|---|---|---|
/share/{token} |
GET | Expenses as JSON. The token is the credential. ?months=N (1-60) |
/sysapi/share/status |
GET | Current link, with usage history |
/sysapi/share/create |
POST | Create or rotate the link |
/sysapi/share/revoke |
POST | Revoke the link |
Account
| Endpoint | Method | Description |
|---|---|---|
/sysapi/account/me |
GET | Signed-in user's account details |
/sysapi/account/password |
POST | Change password (requires the current one) |
/sysapi/account/username |
POST | Change username, moving all data with it |
Update
| Endpoint | Method | Description |
|---|---|---|
/sysapi/update/version |
GET | Installed version (cheap, no network) |
/sysapi/update/check |
GET | Compare against the repository. ?force=1 bypasses the 60s cache |
/sysapi/update/execute |
POST | Start the update in a detached process |
/sysapi/update/status |
GET | Progress of the running or last update |
Example API Calls
# Get expenses for January 2026
curl "http://localhost:3000/sysapi/expenses?month=2026-01&key=YOUR_API_KEY"
# Create an expense
curl -X POST "http://localhost:3000/sysapi/expense_create?key=YOUR_API_KEY" \
-d "amount=50.00&date=2026-01-15&category_id=1&payment_method_id=1&description=Groceries"
# Get all dashboard data in a single call
curl "http://localhost:3000/sysapi/dashboard-data?month=2026-01&key=YOUR_API_KEY"
# Link two user accounts
curl -X POST "http://localhost:3000/sysapi/users/link?key=YOUR_API_KEY" \
-d "user1=john&user2=jane"
Updating
From the web interface
Open Settings → Application Update. The installed version is shown on load and an available update is detected when the panel opens. Update runs the whole procedure with live progress.
The server restarts as part of the update, which ends your session — the page reloads onto the login screen when it comes back.
From the CLI
python cli.py update # with confirmation
python cli.py update --force # unattended
python cli.py update --no-backup # skip the pre-update archive
python cli.py update --no-restart # update without restarting the server
What happens
- Backup — the whole installation (minus
venv/andbackups/) is archived tobackups/update_backup_<timestamp>.tar.gz. - Download — a shallow clone of
master, with the git history stripped. The clone is verified before anything on disk is touched; an incomplete download aborts with the installation untouched. - Install — the server is stopped, the source tree is replaced,
dependencies are reinstalled into the existing
venv/. - Restart — the server is started again and polled on
/healthuntil it answers.
config.env, data/, backups/ and venv/ are never deleted and never
overwritten by the downloaded tree.
If any step past the swap fails, the previous source tree is restored from the archive and the server is restarted. Your data and configuration are left as they are, so nothing recorded during the update window is lost.
The updater runs detached from the server, from a copy in a temporary directory: it has to outlive both the restart it performs and the source tree it replaces.
Troubleshooting
| Symptom | Where to look |
|---|---|
| Update never starts | data/update.log |
| Update failed | data/update_status.json holds the error |
| Server did not come back | data/server.log, then python cli.py server start -b |
| "An update is already running" | A previous run died; the stale lock clears itself once its process is gone |
Claude Access Link
The simplest way to let Claude read your spending: a secret URL that returns your expenses as JSON. No client-side setup, works from any Claude — desktop, web or mobile.
Create it under Settings → Account → Claude access link, copy it, and ask Claude to read the link.
https://wis.aleksi.systems/share/<secret> last 12 months
https://wis.aleksi.systems/share/<secret>?months=3 last 3 months
The response carries the expenses plus totals by month and by category, so Claude can check its own arithmetic rather than summing hundreds of rows.
Anyone who has this URL can read every expense, with no password. That is what makes it work without setup, and what makes it worth guarding. URLs leak easily — browser history, proxy logs, a message pasted in the wrong place. The link is read-only and can never change your data, the settings panel shows when it was last fetched, and Generate new or Revoke cuts access immediately.
Prefer the MCP server if you want no shared secret at all: it signs in with your own credentials and nothing is exposed publicly.
MCP Server (AI Analysis)
mcp_server/ exposes your expenses to an MCP client (Claude Code, Claude
Desktop) so it can analyse your spending — what looks healthy, what is drifting,
what you are paying for and might not need.
It is read-only: every tool maps to a GET, and nothing there can create,
modify or delete an expense.
python3 -m venv .mcpvenv
./.mcpvenv/bin/pip install -r mcp_server/requirements.txt
cp .mcp.env.example .mcp.env # then fill in WIS_USERNAME and WIS_PASSWORD
.mcp.json at the project root already declares the server, so Claude Code
picks it up on the next start. .mcp.env is gitignored, keeping the password
out of the repository and out of your MCP client configuration.
Then ask things like "look at my last 6 months and tell me what's going well and what isn't" or "what am I subscribed to?".
Full tool list, configuration and troubleshooting: mcp_server/README.md.
Project Structure
WhatISpend/
├── backend/
│ ├── main.py # FastAPI application entry point
│ ├── config.py # Configuration management
│ ├── security.py # Rate limiting, auth middleware
│ ├── storage.py # JSON file storage management
│ ├── models.py # Pydantic data models
│ ├── geolocation.py # GeoIP filtering (optional)
│ ├── excel_generator.py # Excel export functionality
│ ├── updater.py # Standalone detached updater
│ ├── update_manager.py # Update process management
│ └── routes/
│ ├── auth.py # Authentication routes
│ ├── account.py # Username and password changes
│ ├── share.py # Claude access link (secret JSON URL)
│ ├── expenses.py # Expense CRUD operations
│ ├── categories.py # Category management
│ ├── payment_methods.py # Payment method management
│ ├── stats.py # Statistics and analytics
│ └── update.py # Update API endpoints
├── frontend/
│ ├── index.html # Main application HTML
│ ├── setup.html # Setup wizard page
│ ├── app.js # Frontend application logic
│ ├── setup.js # Setup wizard JavaScript
│ ├── styles.css # Application styles
│ ├── setup.css # Setup wizard styles
│ └── favicon.* # Application icons
├── cli/
│ ├── __init__.py
│ ├── commands/
│ │ ├── user.py # User management commands
│ │ ├── server.py # Server control commands
│ │ ├── backup.py # Backup commands
│ │ ├── logs.py # Log viewing commands
│ │ ├── update.py # Update command
│ │ └── interactive.py # Interactive mode
│ └── utils/
│ └── display.py # CLI display utilities
├── scripts/
│ ├── create_user.py # User creation utility
│ ├── delete_user.py # User deletion utility
│ ├── link_users.py # User linking utility
│ └── watchdog.py # Monitoring utility
├── .mcp.json # MCP server declaration (project-scoped)
├── .mcp.env.example # Template for MCP credentials
├── mcp_server/ # Read-only MCP server for AI analysis
│ ├── server.py # MCP tool definitions
│ ├── client.py # Authenticated API client
│ ├── analytics.py # Spending analysis
│ ├── requirements.txt # MCP dependencies
│ └── README.md # MCP setup guide
├── data/ # JSON data storage
│ ├── users.json # User accounts
│ ├── expenses.json # Expense records
│ ├── categories.json # Category definitions
│ ├── payment_methods.json # Payment method definitions
│ ├── user_links.json # User link relationships
│ └── update_status.json # Current update status
├── backups/ # Automatic backup storage
├── cli.py # Main CLI entry point
├── install.sh # Automated installation script
├── requirements.txt # Python dependencies
├── config.env # Application configuration (auto-generated)
├── .gitignore # Git ignore rules
├── ChangeLog # Version history
├── V2.7.0 # Current version file
└── README.md # This file
Security
API Key Authentication
- 32-character hexadecimal key generated on installation
- Required on all
/sysapi/*endpoints - Stored securely in
config.env
Password Security
- Argon2id hashing (winner of the Password Hashing Competition)
- Automatic salt generation per user
- Parameters: m=65536, t=3, p=4 (recommended secure defaults)
Rate Limiting
- Default: 50 requests per second per IP
- Configurable via
RATE_LIMITinconfig.env - Returns HTTP 429 when exceeded
GeoIP Filtering (Optional)
- Restrict access to French IP addresses only
- Requires MaxMind GeoIP license key
- Enable via
GEOIP_ENABLED=true
Session Management
- 24-hour session duration (configurable)
- Secure cookie-based sessions
- Automatic expiration and cleanup
Production Recommendations
- Use HTTPS: Deploy behind Nginx/Apache with SSL
- Firewall: Restrict access to trusted IPs
- Regular Backups: Use
python cli.py backup createregularly - Strong Passwords: Use complex passwords for admin accounts
- Update Dependencies: Keep
requirements.txtup to date - Enable GeoIP: If you want to restrict access to specific regions
Data Management
Storage Format
Data is stored in JSON files in the data/ directory:
users.json:
{
"admin": {
"username": "admin",
"password_hash": "$argon2id$v=19$m=65536,t=3,p=4$...",
"created_at": "2026-03-21T10:00:00Z"
}
}
expenses.json:
{
"1": {
"id": 1,
"user": "admin",
"amount": 45.50,
"date": "2026-03-21",
"category_id": 1,
"payment_method_id": 1,
"description": "Groceries",
"created_at": "2026-03-21T10:30:00Z"
}
}
Backup
# Create a backup
python cli.py backup create -o my-backup
# List backups
python cli.py backup list
# Restore from backup
python cli.py backup restore --file backup-20260403.tar.gz
User Management
Create a user:
python cli.py user create --username <username> --password <password>
List users:
python cli.py user list
Link users:
python cli.py user link --user1 <username1> --user2 <username2>
Unlink users:
python cli.py user unlink --user1 <username1> --user2 <username2>
Delete a user:
python cli.py user delete --username <username> [--delete-data]
CLI (Command Line Interface)
A beautiful and intuitive CLI for managing your Expense Tracker application.
Quick Start
cd WhatISpend
source venv/bin/activate
python cli.py --help
Usage Modes
Interactive Mode (Recommended):
python cli.py interactive
# or
python cli.py -i
# Then type commands:
expense-tracker> user create
expense-tracker> server start --background
expense-tracker> status
expense-tracker> exit
Direct Command Mode:
python cli.py status
python cli.py user create --username john
python cli.py server start --background
python cli.py backup create -o monthly-backup
python cli.py update
Main Commands
| Command | Description |
|---|---|
status |
Show application status |
user create |
Create a new user |
user list |
List all users |
user link |
Link two user accounts |
user unlink |
Unlink two user accounts |
server start |
Start the server |
server stop |
Stop the server |
server restart |
Restart the server |
backup create |
Create a backup |
backup list |
List available backups |
backup restore |
Restore from backup |
logs show |
View logs |
update |
Update application to latest version |
Interactive Mode Shortcuts
u=usersrv=serverbkp=backuph=helpq=quit
For complete CLI documentation, see CLI_README.md.
Troubleshooting
Server won't start
-
Check if port is in use:
sudo lsof -i :3000 -
Check logs:
python cli.py logs show -
Verify configuration:
cat config.env
Can't login
-
Verify user exists:
python cli.py user list -
Reset password:
python cli.py user delete --username <username> python cli.py user create --username <username> --password <new_password>
API key errors
-
Check API key in config.env:
grep API_KEY config.env -
Regenerate if needed:
python3 -c "import secrets; print(secrets.token_hex(16))" # Update config.env with new key
GeoIP issues
- Disable GeoIP if causing problems:
Then restart the server.# In config.env GEOIP_ENABLED=false
CSS/JavaScript issues
- The application uses non-minified source files for easier debugging
- Browser cache is disabled for assets to ensure fresh content
- If styles appear broken, try a hard refresh (Ctrl+Shift+R / Cmd+Shift+R)
Development
Dependencies
fastapi>=0.104.0 # Web framework
uvicorn[standard]>=0.24.0 # ASGI server
pydantic>=2.0.0 # Data validation
argon2-cffi>=23.1.0 # Password hashing
python-multipart>=0.0.6 # Form data parsing
slowapi>=0.1.9 # Rate limiting
geoip2>=4.7.0 # GeoIP filtering
python-jose[cryptography]>=3.3.0 # JWT/Cryptography
passlib>=1.7.4 # Password utilities
rich>=13.0.0 # CLI formatting
click>=8.0.0 # CLI framework
openpyxl>=3.1.0 # Excel export
psutil>=5.9.0 # Process management
Performance Optimizations
- Dashboard API: Consolidated from 9 calls to 1 (
/dashboard-data) - GZip Compression: Enabled for responses > 1KB (20-30% size reduction)
- Non-minified Assets: Source CSS/JS used directly for easier debugging
| Metric | Before | After | Gain |
|---|---|---|---|
| Dashboard API calls | 9 | 1 | -89% |
| Initial load time | ~2-3s | ~1-1.5s | -50% |
| HTML size (compressed) | 52 KB | 40 KB | -23% |
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests
- Submit a pull request
License
MIT License - See LICENSE file for details.
Support
For issues, questions, or contributions:
- Check existing documentation
- Review the code in
backend/andfrontend/directories - Open an issue on the repository
Version History
See ChangeLog for detailed release notes.
| Version | Date | Highlights |
|---|---|---|
| 2.7.0 | Aug. 2026 | Claude access link (secret JSON URL) |
| 2.6.0 | Aug. 2026 | Username and password change from Settings |
| 2.5.0 | Aug. 2026 | Update procedure rewritten, read-only MCP server |
| 2.4.1 | April 2026 | Web update system, Leaflet map fix |
| 2.4 | April 2026 | User linking, CLI update restart, category validation |
| 2.3.4 | April 2026 | Dashboard API optimization, GZip compression, shared categories |
| 2.3 | March 2026 | CLI update command with automatic backup |
| 2.2.1 | March 2026 | Project cleanup and documentation |
| 2.2 | March 2026 | GPS coordinate fixes, setup page CSS |
| 2.1 | March 2026 | Geolocation, dark mode, payment filters |
| 2.0 | Feb. 2026 | FastAPI migration, subcategories, new charts |
| 1.1 | Jan. 2026 | Custom payment methods, CSV export |
| 1.0 | Dec. 2025 | Initial stable release |