Skip to content

finance-api ​

Always-on local service for financial data ingestion, storage, and deterministic quant analysis. Backed by SQLite; powers the finance extension.

Quick start ​

bash
cd packages/finance-api/docker
docker compose up -d

Pulls the multi-arch image ghcr.io/sfiorini/pi-stef/finance-api:latest and starts the service. By default it binds to 127.0.0.1:7780 (localhost only) — if the pi client runs on a different machine, change the port mapping to "7780:7780". See the Docker guide for details, volumes, image tags, and token retrieval.

Native ​

bash
pnpm install
pnpm serve

Verify ​

bash
curl http://127.0.0.1:7780/v1/health
# {"ok":true,"data":{"status":"ok","uptimeS":0}}

Architecture: server vs client ​

finance-api is a server that typically runs in Docker on a machine you control (your laptop, a home server, a VPS). The client is the finance extension, which runs wherever you use pi.

ComponentRuns whereConfig location
finance-api (this package)Docker container or native on a serverEnvironment variables + /data/ volume (Docker) or ~/.pi/sf/finance/ (native)
finance (client extension)Inside pi, on your workstation~/.pi/sf/finance/config.json on your machine

When the server runs in Docker, its filesystem is inside the container — the paths below starting with ~/.pi/sf/finance/ refer to the container's filesystem (mapped to the finance-config Docker volume), not your local machine.

Authentication ​

All endpoints except /v1/health require Authorization: Bearer <token>. On first start the server generates a random token and writes it to ~/.pi/sf/finance/token inside the container (chmod 600). In Docker, retrieve it with:

bash
docker compose exec finance-api cat /root/.pi/sf/finance/token

Copy this token into the client's config.json on your workstation (see finance extension). Override with SF_FINANCE_TOKEN to pin a token.

Configuration ​

Server settings (env vars) ​

These configure the server — set them in docker-compose.yml (environment section) or your shell:

VariableDefaultDescription
SF_FINANCE_HOST127.0.0.1 (0.0.0.0 in Docker)Server bind host
SF_FINANCE_PORT7780Server port
SF_FINANCE_DB~/.pi/sf/finance/finance.db (/data/finance.db in Docker)SQLite database path
SF_FINANCE_TOKEN(auto-generated)Bearer token
SF_FINANCE_DATA_FEEDstooqPrice data feed

Client settings (config.json) ​

The client extension reads its config from ~/.pi/sf/finance/config.json on the machine running pi — this is your workstation, not the server. See finance extension docs for the full schema.

Provider credentials ​

Working providers in this release do not use the server's secrets.json:

  • SnapTrade — credentials live in the client's config.json and are sent per-request. See the SnapTrade guide.
  • SimpleFIN — credentials live in the client's config.json and are sent per-request. See the SimpleFIN guide.
  • File Import — no stored credentials; the file path is provided per-request.

The secrets.json file (at ~/.pi/sf/finance/secrets.json on the server) is for server-side providers. Coinbase uses server-side CDP API keys.

Providers ​

Providers are co-equal — enable any combination, and multiple providers run side by side (e.g. SnapTrade for live brokerage sync and File Import for a bank OFX export).

ProviderKindStatus
File Importbrokerage/banking✅ Working
Coinbasecrypto✅ Working
SnapTradebrokerage✅ Working
SimpleFINbanking✅ Working

⚠️ Cross-provider deduplication is not supported yet. If the same real-world account surfaces through two providers, it appears as two separate accounts — there is no merge logic today. Use one provider per account for now.

File Import ​

Import CSV (holdings/positions) or OFX (transactions/balances) via the API:

bash
curl -X POST http://127.0.0.1:7780/v1/import \
  -H "Authorization: Bearer $(cat ~/.pi/sf/finance/token)" \
  -H "Content-Type: application/json" \
  -d '{"filePath":"/path/to/positions.csv"}'

Remote deployments: the import endpoint also accepts { "content", "filename" } to send file contents directly (raw UTF-8 text) instead of a server-side filePath. This is what the finance extension always uses — it reads the file locally on the pi machine and sends content, so the file never needs to exist on the server.

Full details: exact CSV column specs, numeric parsing rules, known limitations, OFX format docs, export walkthroughs, and troubleshooting — see the File Import guide.

HTTP API ​

Base URL http://127.0.0.1:7780. Responses are { "ok": true, "data": {...} }.

Interactive docs ​

The API ships with auto-generated OpenAPI 3.1 documentation:

  • Swagger UI: http://127.0.0.1:7780/docs — interactive API explorer (try requests live)
  • OpenAPI JSON: http://127.0.0.1:7780/openapi.json — raw spec for importing into Postman, Insomnia, etc.

Both endpoints are public (no auth) and generated from Zod route schemas, so they're always in sync.

Postman collection ​

Import the collection and environment from the finance-api postman/ directory:

  1. Postman → Import → select postman/finance-api.postman_collection.json
  2. Import → select postman/finance-api.postman_environment.json
  3. Set the token variable to your bearer token

Regenerate after route changes: npx tsx packages/finance-api/scripts/gen-postman.mjs

Endpoints ​

MethodPathDescription
GET/v1/healthHealth check (public)
GET/v1/market-statusCurrent market session
GET/v1/holdingsAll holdings
GET/v1/net-worthTotal portfolio value
GET/v1/allocationCurrent allocation
GET/v1/driftDrift vs target
GET/v1/goalsList goals
POST/v1/goalsCreate/update goal
GET/v1/suggestionsPending suggestions
POST/v1/suggestions/dismissDismiss a suggestion
POST/v1/syncTrigger a sync tick
POST/v1/importImport from CSV/OFX
GET/v1/history?symbol=Price history
POST/v1/exportExport (json/sqlite)

See the API reference for per-endpoint request/response schemas.

Data model ​

SQLite tables: accounts, holdings, transactions, prices, lots, goals, suggestion_records, market_sessions. Versioned migrations; future changes are additive.

Scheduler & quant engine ​

A session-aware scheduler runs periodic ticks: ingest → refresh prices → recompute suggestions (drift, rebalance, risk, DCA). All numbers are computed by pure, deterministic functions — the LLM client cites them verbatim and never recomputes. POST /v1/sync triggers a tick on demand.

Disclaimer ​

This is not financial advice. Suggestions are informational only — no trades are executed automatically.