Home Docs API reference
Reference

API reference

Capitrack exposes a RESTful JSON API for every operation in the app. Everything you can do in the UI you can automate — managing accounts, recording transactions, importing CSVs, tracking goals, fetching prices and reading settings.

Base URL & conventions

All endpoints are mounted under /api on the host you run Capitrack on. In the normal Docker setup the browser calls them on the same origin as the web app (port 3000) and nginx proxies them to the API container.

base urlhttp
http://localhost:3000/api
  • JSON is snake_case. Request and response bodies use snake_case (base_currency, account_id, tag_ids, change_percent…). The one exception is the change-password endpoint, which uses camelCase (currentPassword, newPassword). Dictionary-keyed responses (the /prices/quotes map keyed by symbol) keep their original keys.
  • IDs in paths are integers; dates are YYYY-MM-DD strings.
  • Error shape: failures return { "error": "message" }; simple successes often return { "message": "..." }.

Authentication

Capitrack uses a session cookie. Call POST /api/auth/login to start a session; the server sets the capitrack.sid cookie (HttpOnly, 7-day sliding expiry). Send that cookie with every subsequent request. All endpoints require it except POST /auth/login, POST /auth/logout, GET /auth/session and GET /health, which are anonymous. Unauthenticated calls return 401 (not a redirect).

MethodEndpointDescription
post/auth/loginAuthenticate with username & password (anonymous)
post/auth/logoutDestroy the current session (anonymous)
get/auth/sessionReturn the signed-in user (anonymous)
put/auth/passwordChange the password (camelCase body)
put/auth/currencySet the base currency

Log in

requestbash
$ curl -X POST http://localhost:3000/api/auth/login \
    -H "Content-Type: application/json" \
    -c cookies.txt \
    -d '{ "username": "admin", "password": "your-password" }'
200 OKjson
{
  "username": "admin",
  "base_currency": "EUR"
}

Returns 401 { "error": "Invalid credentials" } for a bad username/password.

Change password

This endpoint is the exception that uses a camelCase body.

PUT /api/auth/passwordjson
{
  "currentPassword": "your-password",
  "newPassword": "N3w!Secret"
}
New passwords must be at least 8 characters and include an uppercase letter, a lowercase letter, a number and a special character (! @ # $ % ^ & *).

Accounts

MethodEndpointDescription
get/accountsList all accounts (newest first, with tags)
post/accountsCreate an account (only name required)
get/accounts/{id}Get a single account
put/accounts/{id}Partial update (tag_ids replaces the tag set)
del/accounts/{id}Delete an account (cascades to its transactions)
get/accounts/{id}/holdingsPer-symbol holdings summary
del/accounts/purge/allDanger zone — wipe all accounts, transactions, goals, tags & cached prices

Create an account

requestbash
$ curl -X POST http://localhost:3000/api/accounts \
    -H "Content-Type: application/json" -b cookies.txt \
    -d '{ "name": "Stocks", "type": "stock", "currency": "USD",
         "icon": "chart-line", "color": "#10b981", "tag_ids": [] }'

Omitted fields default: type=general, currency=EUR, icon=wallet, color=#6366f1. Returns 201 with the created account, or 400 if name is empty.

Transactions

A transaction's type is one of buy, sell, transfer_in, transfer_out, dividend, interest, fee.

MethodEndpointDescription
get/transactionsList, filterable by account_id, symbol, limit, offset
post/transactionsRecord a transaction (account_id, symbol, type, date required)
get/transactions/{id}Get a single transaction
put/transactions/{id}Partial update
del/transactions/{id}Delete a transaction
get/transactions/export/csvExport transactions as CSV (optionally per account_id)
post/transactions/import/csvImport a CSV file (multipart/form-data)
post/transactions/import/detectDetect a CSV's format without importing

List transactions

requestbash
$ curl -b cookies.txt \
    "http://localhost:3000/api/transactions?account_id=1&limit=50"
200 OKjson
[
  {
    "id": 12,
    "account_id": 1,
    "account_name": "Stock Portfolio",
    "symbol": "AAPL",
    "type": "buy",
    "quantity": 10.0,
    "price": 150.0,
    "fee": 1.0,
    "currency": "USD",
    "date": "2024-02-01",
    "tags": []
  }
]

Import a CSV

multipart/form-data with file (required), account_id (required) and an optional format to force a parser. The same response shape is returned by the UI:

requestbash
$ curl -X POST http://localhost:3000/api/transactions/import/csv \
    -b cookies.txt \
    -F "[email protected]" -F "account_id=1"
200 OKjson
{ "imported": 8, "skipped": 2, "total": 10, "errors": [], "format": "revolut-stocks" }

See CSV import for the supported formats and field mapping.

Goals

MethodEndpointDescription
get/goalsList goals (ordered by target date), filterable by category_id / tag_id
post/goalsCreate a goal (title and target_date required)
get/goals/{id}Get a single goal
put/goals/{id}Partial update (incl. achieved)
del/goals/{id}Delete a goal
del/goalsDelete all goals

Tags

MethodEndpointDescription
get/tagsList tags (alphabetical)
post/tagsCreate a tag (name required; color defaults to #6366f1)
get/tags/{id}Get a single tag
put/tags/{id}Update name / color
del/tags/{id}Delete a tag

Currencies

MethodEndpointDescription
get/currenciesList manual FX rates
post/currenciesUpsert a rate by (from_currency, to_currency)
put/currencies/{id}Update a rate by id
del/currencies/{id}Delete a rate
get/currencies/convertConvert an amount — query from, to, amount
GET /api/currencies/convert?from=USD&to=EUR&amount=100json
{ "result": 92.0, "rate": 0.92 }

If from == to the amount is returned unchanged with rate: 1. Returns 404 if no rate exists for the pair.

Prices

Quotes come from Yahoo Finance and carry only the fields the quote/chart source provides (symbol, price, currency, name, change_percent); richer fields like P/E or market cap are not fetched. Quotes are cached for 5 minutes with a stale fallback.

MethodEndpointDescription
get/prices/quote/{symbol}Live quote for one symbol
post/prices/quotesBatch quotes — body { "symbols": [...] }; returns a map keyed by symbol
get/prices/history/{symbol}Historical OHLCV — query period (1w5y, max)
get/prices/search/{query}Symbol search via Yahoo
get/prices/dashboard/summaryTotal wealth, cost & gain in the base currency, plus per-account breakdown
get/prices/portfolio/historyPortfolio value over time — query account_id, period
get/prices/daily-wealthSaved daily-wealth snapshots between start & end
post/prices/daily-wealthCompute & upsert today's snapshot from cached prices

Dashboard summary

GET /api/prices/dashboard/summaryjson
{
  "total_wealth": 25340.55,
  "total_cost": 21000.0,
  "total_gain": 4340.55,
  "total_gain_percent": 20.67,
  "base_currency": "EUR",
  "accounts": [ { "account_id": 1, "account_name": "Crypto Portfolio", "market_value": 12000.0, "cost_basis": 9000.0, "holdings_count": 2 } ],
  "holdings_count": 5
}

Settings

MethodEndpointDescription
get/settingsApp info — db path, version, repository, license
get/settings/databaseCurrent SQLite path and whether the file exists
put/settings/databaseSet the SQLite path (takes effect on restart)
post/settings/refreshRefresh hook
get/settings/aboutName, description, version, license, author, repository

Health

MethodEndpointDescription
get/health(anonymous, not under /api) — returns { "status": "ok" }; used by the API container's healthcheck

Status codes

CodeMeaning
200Success (reads, updates, deletes)
201Resource created
400Missing or invalid fields
401Not authenticated / bad credentials
404Resource or symbol not found
500Upstream / price error
Because the API runs entirely on your own server, you can script backups, bulk-import history or build custom dashboards without anything leaving your network. The full reference lives in docs/api.md.