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 valuationGET /portfolio/inventory- holdings and savings plansGET /portfolio/cash- buying and withdrawal powerGET /portfolio/timeseries- time-weighted return- more:
/watchlist,/interest-rates,/pending-orders
Securities
GET /securities/:isin- details with live quoteGET /securities/:isin/timeseries- price history- more:
/tick,/tradability,/buyable
Transactions and Savings
GET /transactions- paginated, filterable by ISIN, type and statusGET /savings,GET /savings/transactions
Streaming
GET /valuation/stream- SSE with live portfolio valuationGET /quotes/stream?isins=ISIN1,ISIN2- SSE with live quotes
Other
POST /proxy- raw GraphQL passthroughGET /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.
