Open Source
2026-03

Unofficial Scalable Capital API: Portfolio Data via REST and SSE

Scalable Capital doesn't have a public API. So I built a local proxy that exposes portfolio data via REST and SSE: valuations, quotes, transactions, and live streams.

TypeScriptNode.jsPuppeteerGraphQLSSE
Unofficial Scalable Capital API: Portfolio Data via REST and SSE preview

I invest through Scalable Capital and wanted my portfolio data in my own dashboard. There's no public API, so I built a local proxy.

The Login Problem

The first obstacle was authentication. Hardcoding credentials wasn't an option, and scraping the login form would break with any frontend change.

The solution: Puppeteer opens a real Chromium window, I log in myself including 2FA, and the script waits until the SPA has fully loaded. It then extracts session cookies, the portfolio ID, and other identifiers needed for subsequent API calls.

The session is persisted to disk and restored on the next server start, so a restart doesn't force a re-login. If a request comes back with a 401 or 403, the login flow is triggered automatically and the request is retried once.

The Actual API

Scalable Capital's app talks to a private GraphQL endpoint internally. Real-time data flows over a WebSocket using the graphql-transport-ws protocol.

I implemented the protocol by hand rather than using a ready-made client library, to keep the reconnect and subscription logic transparent.

WebSocket Singleton and SSE Bridge

A single WebSocket connection to the upstream is shared across all connected SSE clients. It's opened on the first client and torn down when the last one disconnects.

[SSE client 1] --+
[SSE client 2] --+-- SubscriptionManager -- single WS sub -- upstream WS
[SSE client N] --+

For quote streams, QuoteManager tracks the union of all requested ISINs across clients. When a new client joins with different ISINs, the upstream subscription is updated and each incoming tick is filtered per client before forwarding.

REST clients get this transparently: GET /portfolio returns a cached WebSocket tick if it's fresh enough, and waits briefly for the next message if not.

Endpoints

Auth

  • POST /auth/login, GET /auth/status, DELETE /auth/logout

Portfolio

  • GET /portfolio - current valuation
  • GET /portfolio/inventory - holdings and savings plans
  • GET /portfolio/cash - buying and withdrawal power
  • GET /portfolio/timeseries - time-weighted return
  • more: /watchlist, /interest-rates, /pending-orders

Securities

  • GET /securities/:isin - details with live quote
  • GET /securities/:isin/timeseries - price history
  • more: /tick, /tradability, /buyable

Transactions and Savings

  • GET /transactions - paginated, filterable by ISIN, type and status
  • GET /savings, GET /savings/transactions

Streaming

  • GET /valuation/stream - SSE with live portfolio valuation
  • GET /quotes/stream?isins=ISIN1,ISIN2 - SSE with live quotes

Other

  • POST /proxy - raw GraphQL passthrough
  • GET /docs - Scalar UI from the OpenAPI spec

API Change Detection

The --monitor flag enables drift detection. On every response, the tool compares the payload structure against a stored baseline and logs any changes (new or missing fields, type changes) with a timestamp to a separate file. This makes it easy to notice when Scalable Capital updates their internal API.

Security

The server only binds to localhost and is never reachable on the network. A token header for all routes can optionally be enforced. The session file is git-ignored and written with restrictive file permissions.


Unofficial project, no affiliation with Scalable Capital. Use at your own risk.