zfin-server/README.md

4.8 KiB

zfin-server

HTTP data service backed by zfin's financial data providers. Serves cached market data over HTTP for two consumers:

  1. LibreCalc spreadsheets — trailing returns via WEBSERVICE/FILTERXML
  2. zfin CLI/TUI — raw SRF cache files as an L1 data cache

Quick start

# Set API keys (same as zfin)
export TWELVEDATA_API_KEY=...
export POLYGON_API_KEY=...

# Start the server
zig build run -- serve --port=8080

# In another terminal
curl http://localhost:8080/AAPL/returns
curl http://localhost:8080/AAPL/returns?fmt=xml

Endpoints

Route Content-Type Description
GET / text/html Landing page
GET /help text/plain Endpoint documentation
GET /symbols application/json List of tracked symbols
GET /:symbol/returns application/json Trailing 1/3/5/10yr returns + volatility
GET /:symbol/returns?fmt=xml application/xml Same, XML for LibreCalc
GET /:symbol/quote application/json Latest quote
GET /:symbol/candles application/x-srf Raw SRF cache file
GET /:symbol/dividends application/x-srf Raw SRF cache file
GET /:symbol/earnings application/x-srf Raw SRF cache file
GET /:symbol/options application/x-srf Raw SRF cache file
GET /:symbol/classification application/x-srf Wikidata classification (SRF)
GET /:symbol/etf_metrics application/x-srf EDGAR NPORT-P fund metrics; 404 for non-funds
GET /:cik/entity_facts application/x-srf EDGAR XBRL entity facts (CIK-keyed)
GET /_edgar/tickers_funds application/x-srf EDGAR mutual-fund ticker map (~3 MB)
GET /_edgar/tickers_companies application/x-srf EDGAR company ticker map (~5 MB)

The SRF endpoints behave as an L2 cache. A present cache file is served as-is (even if stale -- the refresh cron is the freshness authority). On a miss the server fetches once from the upstream provider, fills the cache, and serves the result; it returns 404 only if that fetch also fails. A miss never adds the symbol to the tracked set, so an ad-hoc lookup doesn't create recurring refresh load.

Authentication

/, /help, and /:symbol/returns (the LibreCalc endpoint) are always public. Every other endpoint -- the raw SRF cache files, /quote, /symbols, and the EDGAR ticker maps -- is gated behind a shared key when ZFIN_SERVER_API_KEY is set:

export ZFIN_SERVER_API_KEY=your-secret

# Supply the key as a header (preferred)...
curl -H "X-API-Key: your-secret" http://localhost:8080/AAPL/candles
# ...or as a query parameter (curl convenience)
curl "http://localhost:8080/AAPL/candles?api_key=your-secret"

If ZFIN_SERVER_API_KEY is unset or empty, the server is fully open -- a soft cutover so the key can roll out to clients before enforcement is switched on.

LibreCalc usage

=FILTERXML(WEBSERVICE("http://localhost:8080/AAPL/returns?fmt=xml"), "//trailing1YearReturn")

XML response format:

<returns>
  <ticker>AAPL</ticker>
  <returnDate>2026-03-07</returnDate>
  <trailing1YearReturn>12.34000</trailing1YearReturn>
  <trailing3YearReturn>8.56000</trailing3YearReturn>
  <trailing5YearReturn>15.78000</trailing5YearReturn>
  <trailing10YearReturn>22.10000</trailing10YearReturn>
  <volatility>18.50000</volatility>
</returns>

Cache refresh

Use cron to keep the cache warm for all symbols in your portfolio:

# Refresh all portfolio symbols daily at 5pm ET (after market close)
0 17 * * 1-5 cd /path/to/workdir && ZFIN_PORTFOLIO=portfolio.srf zfin-server refresh

The candle cache TTL is 23h45m (not a full 24h), providing a 15-minute jitter buffer so daily cron runs at the same time always see stale data and trigger a fresh fetch. Dividend and earnings TTLs are 14 and 30 days respectively.

The refresh command reads the portfolio SRF file (defaults to portfolio.srf in the current directory if ZFIN_PORTFOLIO is not set), extracts all stock and watch symbols, and fetches candles, dividends, and earnings for each.

Building

Requires Zig 0.16.0.

zig build        # build
zig build test   # run tests
zig build run -- serve --port=8080

Configuration

All configuration is via environment variables:

Variable Required Description
TWELVEDATA_API_KEY Yes TwelveData API key
POLYGON_API_KEY No Polygon API key
FINNHUB_API_KEY No Finnhub API key
ZFIN_USER_EMAIL Yes Contact email for SEC EDGAR User-Agent header
ZFIN_PORTFOLIO No Path to portfolio SRF (default: portfolio.srf)
ZFIN_CACHE_DIR No Cache directory (default: ~/.cache/zfin)
ZFIN_SERVER_API_KEY No If set, require this key on every endpoint except /, /help, and /:symbol/returns

License

MIT