Endpoints
IndicaView has no API of its own. There is no application server, no REST or GraphQL surface, and no database of its own. What follows is the complete inventory of network activity the project produces, in both directions.
One service is involved if and only if you sign in — optional Supabase, for a profile only. See Authentication.
If you are looking for an API to call, there isn’t one. To get data out, use the CSV / JSON export or the Share card in the optimizer results and Analysis surfaces — all client-side — or read the artifacts a CLI run writes to disk. See Storage & persistence.
Outbound: Binance Spot (public, keyless)
Base URL https://api.binance.com. All three routes are public market-data endpoints:
no API key, no signature, no Binance account, and CORS-permitted for browser use.
| Route | Called from | Purpose |
|---|---|---|
GET /api/v3/exchangeInfo?permissions=SPOT | Browser — app/app.js | Populates the symbol search modal with base/quote assets and display names |
GET /api/v3/ticker/24hr | Browser — app/app.js | 24-hour quoteVolume per symbol, used to rank the search list by liquidity |
GET /api/v3/klines | Python — pinets_cli/dataset.py | Historical OHLCV for sweeps, hyperopt and prop-firm runs |
exchangeInfo and ticker/24hr
Both are fetched with cache: 'no-cache' each time the symbol modal opens, so the volume
ranking stays fresh for the modal session and is not persisted afterwards. Together they are
a few kilobytes — roughly 400 USDT pairs.
exchangeInfo is filtered down to symbols that are simultaneously status === 'TRADING',
isSpotTradingAllowed, and quoteAsset === 'USDT'.
klines
Requested with symbol, interval, startTime and endTime (interval follows Binance
notation: 1m, 5m, 15m, 1h, 4h, 1d, 1w).
Binance caps a klines response at 1000 rows, so the Python fetcher walks forward: it
issues a request, advances the cursor past the last row’s openTime, and repeats until the
range is covered or a short page is returned. It sleeps 50 ms between pages and uses a 15 s
per-request timeout. Returned columns are mapped positionally:
openTime, open, high, low, close, volume,
closeTime, quoteAssetVolume, trades,
takerBuyBase, takerBuyQuote, _ignoreHow the pagination loop actually terminates
There are exactly two exits, and neither of them is the timeout.
- Range covered — the cursor has advanced past
endTimeand the requested window is complete. - Short page — a response carrying fewer than 1000 rows ends the walk. The fetcher stops rather than issuing another request for a page that holds no more data.
The 15 s timeout is per request, not for the walk as a whole: a long range is a
sequence of individually-bounded requests separated by 50 ms sleeps. The column map above
names twelve fields positionally, the twelfth being _ignore.
Results are cached to Parquet. The cache key is the first 16 hex characters of
sha256("binance|{symbol}|{interval}|{start_ms}|{end_ms}"), so an identical range request
never re-hits the network.
What the cache key is built from
The digest covers the entire request shape — symbol, interval, and both time
bounds, joined by pipes. Because start_ms and end_ms are part of
the key, a request for the same range resolves to the same Parquet file and costs no
network call at all.
Only the first 16 hex characters of the digest are kept, so the filename stays short while the binding between key and range stays exact.
Charting in the browser
The chart does not use fetch directly. It goes through Vela’s BinanceProvider, which
paginates internally in 1000-bar chunks. The project wraps that provider in
app/idb_bar_cache.js (window.tradBars), which overrides getBars() — Vela’s only public
fetch entry point — and adds a persistent cache in front of it.
The browser-side cache is a single method override
Everything the project caches in the browser sits behind one function.
BinanceProvider is the chart’s only route to the network, and
getBars() is that provider’s only public fetch entry point — so overriding
that single method in app/idb_bar_cache.js, published as
window.tradBars, is what puts IndexedDB and the throttle in front of every
chart load.
The 1000-bar chunking is handled inside the provider, so the override sits above the pagination rather than inside it. Nothing downstream has to know the walk happened.
Rate limits
Binance’s public endpoints are limited per IP, not per key: 6 000 request-weight per minute on a fixed-clock window. A klines call costs 2 weight and returns at most 1000 bars.
Because the limit is per-IP and the public klines route takes no key, the risk is shared with
anything else on the same connection. app/idb_bar_cache.js defends against this:
- Soft throttle — it inserts a 50 ms delay once estimated weight in the current minute window passes 3 000 (50 % of budget).
- 429 handling — a soft budget reduction, never a silent retry storm.
Why the weight budget is an estimate
Vela does not surface the X-MBX-USED-WEIGHT-1M response header, so the cache
cannot read how much of the budget it has actually spent. It approximates instead, at
2 weight per cache miss — the same price Binance charges a klines call.
That estimate is measured against a fixed-clock window of 6 000 weight per minute, and the 50 ms delay switches on at 3 000, which is 50 % of budget. The approximation errs on the side of throttling too early, which is the right direction to be wrong in against a limit you cannot observe.
Binance’s ban escalation is aggressive: repeated offences step from minutes up to three days. Because the limit is per-IP, a VPN changes the bucket but not the behaviour that filled it. Treat 429/418 as a signal to slow down, not to route around.
Outbound: LuxAlgo prop-firm directory
GET https://app.luxalgo.com/api/propfirms/listCalled from propfirm/firms.py, which declares it as DEFAULT_URL and allows it to be
overridden with the LUXALGO_PROPFIRMS_URL environment variable.
It is a keyless, read-only directory of prop firms and their challenges. Responses are
cached to propfirm/firms_cache/directory.json with a 24-hour TTL, so repeated sweeps do not
re-fetch. This is the only outbound call in the prop-firm track; the Monte Carlo evaluation
itself runs as a local subprocess, not over HTTP.
Outbound: Supabase (optional, sign-in only)
If — and only if — you sign in, the app talks to the Supabase project named by
SUPABASE_URL. Two things happen there, and nothing else:
- Authentication — sign up, sign in, confirmation, recovery and email change, driven by the vendored Supabase SDK rather than hand-written requests.
- One profile row — your display name and contact phone, written with an ownership-filtered update.
Why the endpoints are not listed path by path
Because the app does not construct them. It uses the Supabase SDK, so the request paths are the SDK’s, against whatever host you configure. Naming them here would be describing a library rather than this product, and it would drift the moment the SDK changed.
What matters for the audit is the scope: identity and a profile row. No backtest, cache, strategy source or optimizer result is ever sent.
Local server
The browser application is served as static files.
http://localhost:8080npm run dev copies the vendor bundles and then runs http-server app -p 8080 -c-1 over the
app/ directory. It serves HTML, CSS, JavaScript, the vendored browser bundles and the
.pine files. It accepts GET and HEAD; there is no write path.
http-server binds all interfaces unless you pass -a. On a
shared or untrusted network that exposes the workspace to anyone who can reach the port.
Bind it to loopback explicitly:
npx http-server app -p 8080 -a 127.0.0.1 -c-1What the project never sends
- No telemetry or analytics. There is no beacon, no error reporting service, no third-party script, and no external font or stylesheet request. The page runs fully offline once vendored.
- No writes to Binance. Only public read routes are touched. No order, account, trade or withdrawal endpoint is called, and no exchange credential exists to call one with.
- No browser-crypto of yours. There is no wallet connection, no signing, and no key material anywhere in the codebase.
- No run data, ever. Not by the exports and not by the share card — neither makes a network request. Signing in sends your email address to Supabase and, if you set one, your display name and contact phone. That is the entire list.
Internal channels (not HTTP)
Two parts of the application talk over channels that are not network requests, and are worth knowing about because they behave like APIs:
- Web Worker pool. The optimizer UI and its evaluation workers exchange structured
messages over
postMessage. See Optimizer for the message types. - Detached analysis window. When a run is opened in a separate browser window, the two same-origin documents coordinate directly. No server is involved.
The two channels that behave like APIs
Both carry traffic that looks like a call and returns a result, with no socket, no port and no server in between. That is why they are easy to forget when you are auditing the surface — and why a request capture will not show either of them.
- Web Worker pool — the optimizer UI and its evaluation workers exchange
structured messages over
postMessage. The message types are listed on the Optimizer page. - Detached analysis window — two same-origin documents coordinate directly when a run is opened in a separate window. There is no server in the path at all.
So the check below is a test of the external surface only. An internal channel that misbehaves leaves no trace in the network panel.
Verifying the surface yourself
# The dev server answers on loopback…
curl -I http://localhost:8080/
# …and the app shell is static, not dynamic.
curl -s http://localhost:8080/ | head -n 5For the browser side, the network panel is the honest check: a session that only reads
candles and opens the symbol modal should show requests to api.binance.com and nothing else
beyond your own origin. A capture that shows any other third-party host is a finding worth
reporting.