http://localhost:8000
OAuth-only authentication via Google and GitHub. The API issues JWT Bearer tokens after OAuth login.
GET /api/auth/oauth/providers— Returns enabled OAuth providers (Google, GitHub) with authorize URLs- Frontend redirects user to provider's authorize URL
- Provider redirects back to
{frontend_url}/auth/callback/{provider}?code=... - Frontend sends code to
POST /api/auth/oauth/{provider}/callback - Backend exchanges code for user info, creates/links user, returns JWT tokens
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/auth/oauth/providers |
No | List enabled OAuth providers |
| POST | /api/auth/oauth/google/callback |
No | Exchange Google auth code for JWT |
| POST | /api/auth/oauth/github/callback |
No | Exchange GitHub auth code for JWT |
| POST | /api/auth/refresh |
No | Refresh access token |
| GET | /api/auth/me |
Yes | Get current user profile |
| PUT | /api/auth/me |
Yes | Update profile (name) |
| POST | /api/auth/logout |
Yes | Logout (blacklist token) |
| DELETE | /api/auth/account |
Yes | Delete account permanently |
| POST | /api/auth/account/reset |
Yes | Reset account data (supports mode param) |
All other endpoints require Authorization: Bearer <access_token> header.
POST /api/auth/account/reset
Reset account data while keeping the OAuth login. Supports two modes:
Query Parameters:
mode(string, optional) - Reset scope (default:full)full— Deletes all user data (transactions, analytics, preferences, budgets, goals, account classifications)transactions— Deletes only transactions, import logs, and analytics. Preserves preferences, budgets, goals, and account classifications.
Response (200 OK):
{
"message": "Transactions and analytics cleared. Preferences preserved."
}or (for mode=full):
{
"message": "Account reset to fresh state. All data cleared."
}{
"success": true,
"data": {
/* endpoint-specific data */
},
"message": "Operation successful"
}{
"success": false,
"error": "Error description",
"detail": "Detailed error message"
}POST /api/upload
Upload pre-parsed transaction rows as structured JSON. Files are parsed client-side using SheetJS; the frontend computes a SHA-256 file hash, maps columns, validates rows, and sends the result here.
Request Body (JSON):
{
"file_name": "MoneyManager.xlsx",
"file_hash": "a1b2c3d4e5f6...",
"rows": [
{
"date": "2025-01-15",
"amount": 5000.0,
"type": "Expense",
"category": "Groceries",
"subcategory": "Supermarket",
"account": "HDFC Savings",
"note": "Weekly groceries"
}
],
"force": false
}Fields:
file_name(string, required) - Original file namefile_hash(string, required) - SHA-256 hash of the file (for deduplication)rows(array, required) - Array ofTransactionRowobjects withdate,amount,type,category,subcategory,account,noteforce(boolean, optional) - Force re-import if file hash already exists (default: false)
Response (200 OK):
{
"success": true,
"message": "File uploaded and processed successfully",
"stats": {
"processed": 291,
"inserted": 45,
"updated": 12,
"deleted": 3,
"unchanged": 231
},
"file_name": "MoneyManager.xlsx"
}Error Responses:
- 400: Invalid data or missing required fields
- 409: File already imported (use
force: trueto override) - 500: Server error
Example:
curl -X POST http://localhost:8000/api/upload \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"file_name": "transactions.xlsx",
"file_hash": "abc123...",
"rows": [{"date": "2025-01-15", "amount": 5000, "type": "Expense", "category": "Food", "subcategory": "Dining", "account": "HDFC", "note": ""}],
"force": false
}'GET /api/transactions
Retrieve all transactions from the database.
Query Parameters:
skip(integer, optional) - Number of records to skip (default: 0)limit(integer, optional) - Number of records to return (default: 100)
Response (200 OK):
{
"data": [
{
"id": "abc123def456...",
"date": "2025-01-15",
"amount": 5000.0,
"type": "Expense",
"category": "Groceries",
"subcategory": "Supermarket",
"account": "Checking",
"description": "Weekly groceries",
"file_source": "MoneyManager.xlsx",
"created_at": "2025-01-15T10:30:00Z",
"updated_at": "2025-01-15T10:30:00Z"
}
],
"total": 1234,
"skip": 0,
"limit": 100
}GET /api/transactions/all
Return every non-deleted transaction in a single JSON array. Designed for the frontend analytics layer which needs the full dataset for client-side aggregation.
Query Parameters:
start_date(ISO date, optional) - Start date filterend_date(ISO date, optional) - End date filter
Response (200 OK):
[
{
"id": "abc123def456...",
"date": "2025-01-15",
"amount": 5000.0,
"type": "Expense",
"category": "Groceries",
"subcategory": "Supermarket",
"account": "Checking",
"description": "Weekly groceries",
"file_source": "MoneyManager.xlsx"
}
]GET /api/transactions/search
Search and filter transactions with pagination, sorting, and full-text search across notes, category, and account fields.
Query Parameters:
query(string, optional) - Search text in notes, category, accountcategory(string, optional) - Filter by categorysubcategory(string, optional) - Filter by subcategoryaccount(string, optional) - Filter by accounttype(string, optional) - Filter by type (Income,Expense,Transfer)min_amount(float, optional) - Minimum amountmax_amount(float, optional) - Maximum amountstart_date(ISO date, optional) - Start date filterend_date(ISO date, optional) - End date filterlimit(integer, optional) - Max results to return (1-1000, default: 100)offset(integer, optional) - Number of results to skip (default: 0)sort_by(string, optional) - Sort field:date,amount,category, oraccount(default:date)sort_order(string, optional) -ascordesc(default:desc)
Response (200 OK):
{
"data": [
{
"id": "abc123def456...",
"date": "2025-01-15",
"amount": 5000.0,
"type": "Expense",
"category": "Groceries",
"account": "Checking"
}
],
"total": 234,
"limit": 100,
"offset": 0,
"has_more": true
}GET /api/transactions/export
Export all non-deleted transactions as a CSV file download.
Query Parameters:
start_date(ISO date, optional) - Start date filterend_date(ISO date, optional) - End date filter
Response (200 OK): CSV file with headers: id, date, amount, currency, type, category, subcategory, account, from_account, to_account, note, source_file, last_seen_at
Metadata endpoints for populating dropdowns and filter options. All require authentication.
GET /api/meta/accounts
Return unique account names from all transactions (including transfer from/to accounts).
Response (200 OK):
{
"accounts": ["Checking", "HDFC Savings", "Grow Mutual Funds"]
}GET /api/meta/filters
Return combined filter metadata (transaction types + account names).
Response (200 OK):
{
"transaction_types": ["Expense", "Income", "Transfer"],
"accounts": ["Checking", "HDFC Savings", "Grow Mutual Funds"]
}GET /api/meta/buckets
Return dynamically classified category buckets (needs/wants/savings/investment) based on existing transaction data.
Response (200 OK):
{
"needs": ["Food", "Rent", "Utilities"],
"wants": ["Entertainment", "Shopping"],
"savings": ["Emergency Fund"],
"investment_categories": ["Mutual Funds", "Stocks"],
"investment_accounts": ["Grow Mutual Funds", "Zerodha Stocks"]
}GET /api/analytics/overview
Get high-level financial metrics.
Query Parameters:
time_range(string, optional) - Filter by time rangeall_time(default)last_monthlast_3_monthslast_6_monthslast_year
Response (200 OK):
{
"total_income": 450000.0,
"total_expenses": 285000.0,
"net_change": 165000.0,
"best_month": {
"month": "December",
"net": 45000.0
},
"worst_month": {
"month": "October",
"net": 8000.0
},
"account_distribution": {
"Savings": 250000.0,
"Checking": 150000.0,
"Credit Card": -50000.0
}
}GET /api/analytics/kpis
Get key performance indicators.
Query Parameters:
time_range(string, optional) - Time range filter
Response (200 OK):
{
"total_income": 450000.0,
"total_expenses": 285000.0,
"net_savings": 165000.0,
"savings_rate": 0.3667,
"transaction_count": 1234,
"average_transaction": 365.57,
"expense_count": 945,
"income_count": 289,
"top_category": {
"category": "Rent",
"amount": 120000.0,
"percentage": 0.42
},
"top_income_source": {
"category": "Salary",
"amount": 400000.0,
"percentage": 0.89
}
}GET /api/analytics/behavior
Get behavioral insights about spending patterns.
Query Parameters:
time_range(string, optional) - Time range filter
Response (200 OK):
{
"average_monthly_spending": 23750.0,
"spending_velocity": 2847.25,
"expense_concentration": 0.42,
"top_categories": [
{
"category": "Rent",
"amount": 120000.0,
"percentage": 0.42,
"trend": "stable"
},
{
"category": "Food",
"amount": 45000.0,
"percentage": 0.16,
"trend": "increasing"
}
],
"lifestyle_changes": [
"Increased dining out spending",
"More frequent entertainment expenses"
]
}GET /api/analytics/trends
Get spending and income trends over time.
Query Parameters:
time_range(string, optional) - Time range filter
Response (200 OK):
{
"monthly_trends": [
{
"month": "January",
"income": 37500.0,
"expenses": 23750.0,
"net": 13750.0,
"trend": "up"
}
],
"consistency_score": 0.85,
"surplus_trend": "increasing",
"average_monthly_surplus": 13750.0,
"forecast_next_month": 14200.0
}GET /api/analytics/wrapped
Get text-based insights and narratives about financial year.
Query Parameters:
time_range(string, optional) - Time range filter
Response (200 OK):
{
"summary": "You had a financially strong year with consistent savings.",
"income_narrative": "You earned ₹450,000 from 289 income transactions...",
"expense_narrative": "Your expenses totaled ₹285,000...",
"highlights": [
"Highest spending month was October",
"Food expenses increased 15% YoY",
"Maintained 37% savings rate"
],
"recommendations": [
"Consider reducing discretionary spending",
"Track subscription costs more carefully"
]
}GET /api/calculations/totals
Calculate total income, expenses, and net savings.
Query Parameters:
start_date(ISO date, optional) - Start date filterend_date(ISO date, optional) - End date filter
Response (200 OK):
{
"total_income": 450000.0,
"total_expenses": 285000.0,
"net_savings": 165000.0,
"income_transactions": 289,
"expense_transactions": 945
}GET /api/calculations/monthly-aggregation
Get monthly income and expense data.
Query Parameters:
start_date(ISO date, optional)end_date(ISO date, optional)
Response (200 OK):
{
"months": [
{
"month": "2025-01",
"income": 37500.0,
"expenses": 23750.0,
"net": 13750.0,
"transactions": 45
}
]
}GET /api/calculations/category-breakdown
Get spending by category.
Query Parameters:
start_date(ISO date, optional)end_date(ISO date, optional)transaction_type(string, optional) - "Income" or "Expense"
Response (200 OK):
{
"categories": [
{
"category": "Rent",
"amount": 120000.0,
"percentage": 0.42,
"transaction_count": 12
},
{
"category": "Food",
"amount": 45000.0,
"percentage": 0.16,
"transaction_count": 234
}
],
"total": 285000.0
}GET /api/calculations/account-balances
Get current balance for each account.
Query Parameters:
start_date(ISO date, optional)end_date(ISO date, optional)
Response (200 OK):
{
"accounts": [
{
"name": "Savings",
"balance": 250000.0
},
{
"name": "Checking",
"balance": 150000.0
}
],
"total_balance": 400000.0
}GET /api/calculations/insights
Get comprehensive financial insights.
Query Parameters:
start_date(ISO date, optional)end_date(ISO date, optional)
Response (200 OK):
{
"average_daily_income": 1232.88,
"average_daily_expense": 780.82,
"average_monthly_expense": 23750.0,
"savings_rate": 0.3667,
"largest_transaction": {
"amount": 120000.0,
"category": "Rent",
"date": "2025-01-01"
},
"unusual_spending": [
{
"amount": 95000.0,
"category": "Travel",
"date": "2025-06-15",
"reason": "2x normal spending"
}
]
}GET /api/calculations/top-categories
Get top spending categories.
Query Parameters:
start_date(ISO date, optional)end_date(ISO date, optional)limit(integer, optional) - Number of categories (default: 10)transaction_type(string, optional) - "Income" or "Expense"
Response (200 OK):
[
{
"category": "Rent",
"amount": 120000.0,
"percentage": 0.42,
"count": 12
},
{
"category": "Food",
"amount": 45000.0,
"percentage": 0.16,
"count": 234
}
]GET /api/account-classifications
Get all account classifications set by user.
Response (200 OK):
{
"classifications": [
{
"account_name": "Grow Mutual Funds",
"account_type": "investment"
},
{
"account_name": "HDFC Savings",
"account_type": "savings"
}
]
}GET /api/account-classifications/{account_name}
Get classification for a specific account.
Response (200 OK):
{
"account_name": "Grow Mutual Funds",
"account_type": "investment"
}POST /api/account-classifications
Set or update account classification.
Request Body:
{
"account_name": "Grow Mutual Funds",
"account_type": "investment"
}Response (200 OK):
{
"success": true,
"message": "Account classification saved"
}DELETE /api/account-classifications/{account_name}
Remove account classification.
Response (200 OK):
{
"success": true,
"message": "Account classification removed"
}GET /api/account-classifications/type/{account_type}
Get all accounts of a specific type.
Parameters:
account_type- One of:investment,savings,checking,credit,loan
Response (200 OK):
{
"accounts": ["Grow Mutual Funds", "Zerodha Stocks", "PPF Account"]
}Pre-aggregated analytics data. All require authentication.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/analytics/v2/daily-summaries |
Daily income/expense/net aggregations |
| GET | /api/analytics/v2/investment-holdings |
Investment portfolio holdings |
| GET | /api/analytics/v2/monthly-summaries |
Monthly income/expense/savings |
| GET | /api/analytics/v2/category-trends |
Category-level trends over time |
| GET | /api/analytics/v2/transfer-flows |
Account-to-account transfer flows |
| GET | /api/analytics/v2/recurring-transactions |
Detected recurring patterns |
| GET | /api/analytics/v2/merchant-intelligence |
Merchant spending insights |
| GET | /api/analytics/v2/net-worth |
Net worth snapshots over time |
| GET | /api/analytics/v2/fy-summaries |
Fiscal year summaries |
| GET | /api/analytics/v2/anomalies |
Detected spending anomalies |
| GET | /api/analytics/v2/budgets |
Budget tracking data |
| GET | /api/analytics/v2/goals |
Financial goals and progress |
| POST | /api/analytics/v2/budgets |
Create a new budget |
| POST | /api/analytics/v2/goals |
Create a new financial goal |
| POST | /api/analytics/v2/anomalies/{id}/review |
Mark anomaly as reviewed |
| POST | /api/analytics/v2/refresh |
Recompute all pre-aggregated analytics tables |
POST /api/analytics/v2/refresh
Recompute all pre-aggregated analytics tables (daily summaries, monthly summaries, category trends, transfer flows, merchants, recurring transactions, net worth, investment holdings, FY summaries, anomalies, budgets). Called by the frontend after a successful upload to ensure analytics data is fresh.
Runs synchronously -- the response is only sent after all tables are updated. This replaces the previous BackgroundTasks approach which was unreliable on Vercel serverless.
Response (200 OK):
{
"success": true,
"analytics": {
"daily_summaries": 730,
"monthly_summaries": 24,
"category_trends": 156,
"transfer_flows": 42,
"merchants": 18,
"recurring": 12,
"investment_holdings": 5,
"fy_summaries": 3,
"anomalies": 7,
"budgets_updated": 4
}
}User preference management. All require authentication.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/preferences |
Get all preferences |
| PUT | /api/preferences |
Update preferences (partial) |
| POST | /api/preferences/reset |
Reset to defaults |
| PUT | /api/preferences/fiscal-year |
Update fiscal year |
| PUT | /api/preferences/essential-categories |
Update essential categories |
| PUT | /api/preferences/investment-mappings |
Update investment mappings |
| PUT | /api/preferences/income-sources |
Update income classification |
| PUT | /api/preferences/budget-defaults |
Update budget defaults |
| PUT | /api/preferences/display |
Update display preferences |
| PUT | /api/preferences/anomaly-settings |
Update anomaly settings |
| PUT | /api/preferences/recurring-settings |
Update recurring settings |
| PUT | /api/preferences/spending-rule |
Update 50/30/20 targets |
| PUT | /api/preferences/credit-card-limits |
Update credit card limits |
| PUT | /api/preferences/earning-start-date |
Update earning start date |
| PUT | /api/preferences/salary-structure |
Update salary CTC structure per FY |
| PUT | /api/preferences/rsu-grants |
Update RSU grant list with vesting schedules |
| PUT | /api/preferences/growth-assumptions |
Update growth assumptions (hike %, variable growth, stock appreciation, projection years) |
Preferences include 20 sections: fiscal year, essential categories, investment mappings, income classification, budget defaults, display format, anomaly settings, recurring settings, spending rule targets, credit card limits, earning start date, fixed expense categories, savings/investment targets, payday, tax regime, excluded accounts, notification preferences, salary structure, RSU grants, and growth assumptions.
PUT /api/preferences/salary-structure
Update the salary CTC structure per fiscal year. Each FY key maps to a salary components object.
Request Body:
{
"salary_structure": {
"2025-26": {
"basic_annual": 600000,
"hra_annual": 300000,
"special_allowance_annual": 200000,
"epf_monthly": 1800,
"nps_monthly": null,
"professional_tax_annual": 2400,
"variable_pay_annual": 100000,
"other_annual": 0,
"is_new_regime": true
}
}
}PUT /api/preferences/rsu-grants
Update the list of RSU grants with vesting schedules.
Request Body:
{
"rsu_grants": [
{
"id": "grant-1",
"company": "ACME Corp",
"grant_date": "2025-01-15",
"total_shares": 100,
"stock_price": 1500,
"vesting_schedule": [
{ "date": "2026-01-15", "quantity": 25 },
{ "date": "2027-01-15", "quantity": 25 },
{ "date": "2028-01-15", "quantity": 25 },
{ "date": "2029-01-15", "quantity": 25 }
]
}
]
}PUT /api/preferences/growth-assumptions
Update the assumptions used for multi-year tax projections.
Request Body:
{
"growth_assumptions": {
"salary_hike_pct": 10,
"variable_growth_pct": 5,
"stock_appreciation_pct": 8,
"projection_years": 3,
"include_rsu_in_projection": true
}
}User-provided LLM credentials for the chat widget. Keys are encrypted at rest with AES-256-GCM (PBKDF2-derived from the app's JWT secret + per-ciphertext random 128-bit salt). OpenAI and Anthropic calls go browser-direct using the decrypted key; Bedrock is proxied via the backend because it requires SigV4 auth.
GET /api/preferences/ai-config
Returns the user's AI configuration without the raw key.
Response:
{
"provider": "anthropic",
"model": "claude-sonnet-4-6",
"has_key": true,
"region": null
}For Bedrock, region is set (e.g., "us-east-1"). If the user has not configured AI, all fields are null/false.
PUT /api/preferences/ai-config
Stores AI provider configuration with the key encrypted at rest.
Request Body:
{
"provider": "openai",
"model": "gpt-4.1",
"api_key": "sk-...",
"region": null
}provider must be one of openai, anthropic, bedrock. For Bedrock, set region (e.g. "us-east-1"); api_key is still stored but Bedrock calls currently use the server's AWS credential chain via boto3.
GET /api/preferences/ai-config/key
Returns the decrypted API key for use in browser-direct streaming calls (OpenAI, Anthropic). The frontend calls this on each chat send; the key is never cached in localStorage.
Response:
{ "api_key": "sk-..." }Error Responses:
404if no key is configured400with detail"Cannot decrypt API key -- the server secret likely changed..."if the JWT secret rotated since the key was saved (user must re-enter their key in Settings)
DELETE /api/preferences/ai-config
Clears the stored provider, model, and encrypted key. Returns {"status": "deleted"}.
POST /api/ai/bedrock/chat
Streams a Bedrock converse-stream response as Server-Sent Events. Required because Bedrock needs SigV4 authentication and doesn't support CORS for browser-direct calls. Uses boto3.client('bedrock-runtime').converse_stream() server-side.
Request Body:
{
"messages": [
{ "role": "user", "content": "How much did I spend last month?" }
],
"system_prompt": "You are a financial assistant...",
"max_tokens": 1024
}Response: text/event-stream (SSE). Each event is either a token or an error:
data: {"token": "Based"}
data: {"token": " on"}
data: {"token": " your"}
data: [DONE]
Errors are sent as data: {"error": "..."} followed by data: [DONE].
GET /api/analytics/charts/income-expense
Get monthly income vs expense data for charts.
Response (200 OK):
{
"data": [
{
"month": "Jan 2025",
"income": 150000,
"expense": 85000
}
]
}GET /api/analytics/charts/categories
Get category breakdown for pie/donut charts.
Response (200 OK):
{
"data": [
{
"category": "Food",
"amount": 25000,
"percentage": 0.15
}
]
}GET /api/analytics/charts/monthly-trends
Get monthly trend data for line charts.
Response (200 OK):
{
"data": [
{
"month": "2025-01",
"income": 150000,
"expense": 85000,
"savings": 65000
}
]
}GET /api/analytics/charts/account-distribution
Get account balance distribution.
Response (200 OK):
{
"data": [
{
"account": "HDFC Savings",
"balance": 250000,
"percentage": 0.45
}
]
}GET /api/analytics/insights/generated
Get AI-generated financial insights and recommendations.
Response (200 OK):
{
"insights": [
{
"type": "spending",
"message": "Your food expenses increased 20% this month",
"severity": "warning"
},
{
"type": "savings",
"message": "Great job! You saved 35% of your income",
"severity": "success"
}
]
}GET /api/exchange-rates
Fetch live exchange rates from the European Central Bank (via frankfurter.dev) with 24-hour in-memory cache.
Query Parameters:
base(string, optional) - Base currency (default:INR)
Response (200 OK):
{
"base": "INR",
"rates": {
"USD": 0.01179,
"EUR": 0.01087,
"GBP": 0.00935
},
"updated_at": "2026-04-11T10:30:00Z"
}Fallback behavior: Fresh cache -> stale cache -> hardcoded fallback rates. Returns 502 only if all three tiers fail.
GET /api/stock-price/{symbol}
Fetch the latest regular-market price for a stock ticker via Yahoo Finance. Proxied through the backend to avoid CORS restrictions.
Path Parameters:
symbol(string, required) - Stock ticker symbol (e.g.AMZN,AAPL,GOOGL). Max 10 characters.
Response (200 OK):
{
"symbol": "AMZN",
"price": 186.49,
"currency": "USD"
}Error Responses:
- 400: Invalid symbol (empty or exceeds 10 chars)
- 502: Could not fetch price from Yahoo Finance
| Code | Meaning | Action |
|---|---|---|
| 200 | OK | Success |
| 400 | Bad Request | Check parameters |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | File already imported (use force=true) |
| 500 | Server Error | Contact support |
Rate limiting is implemented using slowapi (based on limits). Endpoints are rate-limited per user/IP to prevent abuse.
CORS is enabled for cross-origin requests. Allowed origins are configurable via the LEDGER_SYNC_CORS_ORIGINS environment variable (JSON array).
Default origins (development):
Production origins are set via the LEDGER_SYNC_CORS_ORIGINS environment variable on Vercel.
Access interactive API documentation at:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc