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 (client-side token cleanup) |
| 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. Fallback responses additionally include fallback: true and fallback_as_of: "YYYY-MM-DD" so the frontend can warn users.
GET /api/rates/instruments
Return EPF/PPF/NPS rates with effective-from / source-url metadata. No reliable public JSON API exists for Indian EPF (EPFO publishes via yearly PDF notification) or PPF (Ministry of Finance publishes quarterly via press release), so these rates are served from a file config at backend/src/ledger_sync/config/instrument_rates.json. Updating a rate is a one-line PR.
Response (200 OK):
{
"updated_at": "2026-05-13",
"epf": {
"rate_pct": 8.25,
"effective_from": "2024-04-01",
"effective_until": null,
"source_url": "https://www.epfindia.gov.in/site_en/WhatsNew.php",
"notes": "..."
},
"ppf": {
"rate_pct": 7.1,
"effective_from": "2025-04-01",
"effective_until": "2026-06-30",
"source_url": "https://dea.gov.in/small-savings-scheme"
},
"nps": {
"default_allocation_pct": {"equity": 50, "corp_bond": 30, "govt_bond": 20},
"historical_return_pct": {"equity": 10.0, "corp_bond": 8.5, "govt_bond": 7.5},
"source_url": "https://npscra.nsdl.co.in/scheme-performance.php"
}
}Consumed by useInstrumentRates() on the frontend, which ships a compiled-in fallback so InstrumentProjections renders zero-network.
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, keyed by remote IP address. Limits applied:
POST /api/auth/refresh-- 20/minutePOST /api/auth/oauth/{provider}/callbackand/api/auth/oauth/{provider}/login-url-- 20/minutePOST /api/upload-- 10/minutePOST /api/ai/bedrock/chat-- 30/minute (complements the per-user daily message cap in app_bedrock mode)
Over-limit requests receive HTTP 429 via a globally registered exception handler. Other endpoints are unthrottled; add @limiter.limit(...) in the relevant router module to protect new sensitive endpoints.
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