idxby Midori Research
Stockbitdocs/stockbit.mdVersion 0.8.2

Stockbit realtime (opt-in)

idx reads IDX data from free, keyless public endpoints. Stockbit is the one exception: an opt-in provider that serves realtime quotes, history and a live order book from Stockbit's own backend, carrying your own Stockbit account session. It is never in the provider auto chain: quotes and history use it only when you ask for it with --provider stockbit. The same session also reads what only Stockbit has in one place: every corporate action of a stock since its listing, the market's corporate-action calendar, and the insider and 5 % holder movements filed with IDX and KSEI (idx stockbit actions, idx stockbit calendar, idx stockbit movements). And idx broker lists every broker that bought and sold a stock from it, by itself once you are logged in (see Broker summary).

This uses an unofficial, undocumented API with your personal account. It may conflict with Stockbit's Terms of Use, and automated access could get an account flagged. Use your own account, at your own risk, at a human-ish request rate. idx is not affiliated with or endorsed by Stockbit.

Log in once

idx stockbit login

idx never asks for your Stockbit password. You sign in on stockbit.com itself, and idx takes only what that sign-in leaves behind: the credentialStorage cookie, which holds the session's two tokens. Whichever way it arrives, idx first stores it (0600), then checks it with one request to Stockbit; when Stockbit refuses it, the previous session is put back (or none, if there was none) and nothing changes. When the session already stored still works and idx can renew it, login says so and stops; --force signs in anyway.

In a browser idx opens (the default on a desktop): idx launches a Chromium-family browser (Chrome, Edge, Brave; --browser or IDX_BROWSER chooses one) on Stockbit's sign-in page, with a profile only idx uses (stockbit-chrome-profile beside the session file; --profile moves it). You sign in there, with whatever Stockbit asks (password, OTP, captcha). idx reads nothing but cookies over the Chrome DevTools Protocol: it intercepts no request, so the password never passes through idx. Once Stockbit accepts the session, idx closes Stockbit's tab, deletes the cookie from its profile (so a later run cannot pick up a pair idx has since rotated) and closes the browser. The profile keeps Stockbit's other cookies, so the next sign-in may skip a step. Google sign-in refuses browsers driven this way ("This browser or app may not be secure"); use the local page instead.

Through a local page (--page; also chosen by itself over SSH, without a display, or without a Chromium-family browser):

idx stockbit login --page                 # prints http://127.0.0.1:7878/login/<secret>
idx stockbit login --page --port 7878     # pin the port, e.g. for an SSH forward

idx serves a page on 127.0.0.1 only, at a path with a random secret, for the browser you already use (any browser, Firefox included). Over SSH it also prints the forward to run on the machine with the browser, for example ssh -N -L 7878:127.0.0.1:7878 you@server; then open the URL there. On the page:

  1. Open a private window and sign in at stockbit.com (Google sign-in works here). idx then holds a session of its own while your everyday browser keeps its own.
  2. Drag Send to idx to the bookmarks bar, then click that bookmark on the stockbit.com tab. It runs only on stockbit.com, reads credentialStorage there and opens the page with the value in the address fragment, which the browser never sends to any server. A bookmark from an earlier run carries an old secret: drag it again.
  3. Or paste the cookie into the page (DevTools > Application > Cookies, or the clipboard button).

The page posts only that value to idx, which checks it with Stockbit and answers on the page. Close the private window afterwards without signing out. The page answers only on its secret path, only to a loopback Host, and takes a POST only from a loopback origin; it keeps no copy, sets no-store, no-referrer and a CSP that allows nothing outside the page, accepts one session and stops (or after --timeout, 300 seconds by default). Port 7878 is the default; when it is taken idx picks a free one, --port pins it and --port 0 takes any.

By pasting the cookie, from any browser:

idx stockbit login --manual                  # paste it into this terminal
pbpaste | ssh you@host idx stockbit login    # or pipe it; a pipe on stdin means paste

Sign in at stockbit.com (a private window keeps your everyday browser's session apart, see One refresher per session), open DevTools (F12) > Application > Cookies > https://stockbit.com and copy the value of credentialStorage. A bare refresh token works too. Close that window afterwards without signing out: signing out there may end the session idx now holds.

The session is stored locally (see Where the session lives) and refreshed for you from then on. Check it any time:

idx stockbit status        # logged in? when do the tokens expire?
idx stockbit logout        # delete the stored session

status reads the stored tokens' own expiry and sends nothing. The access token lasts about a day and the refresh token about a week. idx refreshes only when a request needs it (the access token has expired, expires within the hour, or Stockbit refused it), and each refresh replaces both tokens, so a request made while the access token is still good renews nothing. The refresh token therefore runs out about a week after the last refresh, which can be up to a day short of a week after the last use; using idx at least every six days keeps the session working. Once the refresh token has run out too, status says logged in no, with a note that the session expired and that idx stockbit login renews it ("logged_in": false, "expired": true in JSON). While only the access token can still be used (its refresh token expired, or none is stored), logged in is yes with a note that the session stops working when that token expires ("renewable": false). A session Stockbit ended early (signed out elsewhere, or refreshed by another tool) still reads as logged in until a request finds out: that request fails naming idx stockbit login, and the process that sent it (a running idx mcp server, say) then reports that Stockbit refused the refresh token; idx stockbit status, a process of its own, cannot tell.

Use it

Once logged in, point any quote/history command at the provider:

idx --provider stockbit stocks quote BBCA BBRI      # realtime, not delayed
idx --provider stockbit stocks history BBCA --period 1mo
idx --provider stockbit stocks intraday BBCA        # today's one-minute bars, realtime
idx config set provider stockbit                    # make it the default
idx config set history_provider stockbit            # or: candles only, free providers for the rest

history_provider (--history-provider, IDX_HISTORY_PROVIDER) picks where candles come from apart from provider. With history_provider = "stockbit" the free providers keep serving quotes, fundamentals, financials and the rest while history, technicals, returns and the brief take Stockbit's candles. Under provider = "stockbit" the default history_provider = "auto" keeps on Stockbit only the requests whose bars Stockbit returns as asked (daily over 1mo to 1y, hourly over 5d, weekly over 5y, same-day intraday) and sends the rest (2y or 5y daily, 1y weekly, any monthly bars) to Yahoo with a note; when the session fails it falls back to Yahoo with a warning. history_provider = "stockbit" keeps every window on Stockbit. See configuration.md.

And read the live order book (Stockbit-only — the free sources have no depth):

idx stockbit orderbook BBCA            # depth ladder + session foreign flow
idx stockbit orderbook BBRI --depth 5
idx -o json stockbit orderbook TLKM | jq '.data.bids[0]'

Sizes in the order book are shares (one IDX lot is 100 shares); values are IDR. History candle volumes are converted from Stockbit's lots to shares so they match the other providers. Stockbit chooses the candle granularity and span from the window (1d is one-minute; 5d, and intraday intervals over any longer period, hourly over one week; 1mo to 1y daily; 2y weekly over three years; 5y and longer weekly over five years), and a warning says so whenever those bars are not the --interval asked for or that span is not the --period (--period 1y --interval 1wk gets daily bars and a warning; --period 1mo --interval 1h gets one week of hourly bars and a warning, since the candles are not trimmed to the period; --period 5d --interval 1h gets the hourly bars it asked for and none).

Foreign flow in the order book (foreign_buy, foreign_sell, foreign_net) comes with a flag, has_foreign_bs. While it is false the figures are not the current session's: a BBCA book recorded in session 2 on 2026-11-03 carried a foreign net of -116.1B, which is IPOT's figure for 2026-11-02 (2026-11-03 itself closed at -28.8B). So idx keeps the three figures only while Stockbit marks them current (true, as in idx's own books recorded after the close) and otherwise leaves them empty, with a warning that says why (on stderr in a table, in warnings in JSON). A missing or non-boolean flag counts as not current. The false book came from another account (recorded by 0xrsydn/idx-cli), and its orderbook_active_feature_mobile differs too (ORDER_BOOK_FEATURE_UNSPECIFIED, where idx's books say ORDER_BOOK_FEATURE_FOREIGN_BS), so it is still open whether the flag follows the session clock or an account feature. idx reads only the flag, so either way the figures show only when Stockbit marks them current.

stocks intraday takes its bars where candles come from, so with --provider stockbit or history_provider = "stockbit" it shows today's realtime one-minute bars from your session during trading hours; the default providers serve Yahoo's delayed bars without a login. When the day's window is still empty, idx retries over five days only with a source whose five-day window has the bars asked for: under --provider stockbit that is Yahoo (the last session's bars, with a warning naming its date), while with history_provider = "stockbit" there are no bars until Stockbit has some (Stockbit's five-day window is hourly; the quote still answers).

The Stockbit provider covers quote, history and orderbook. The other commands (fundamentals, financials, news, …) stay on the free providers; run them without --provider stockbit, or keep provider on auto and set only history_provider = "stockbit".

Corporate actions and calendar

idx stockbit actions BBCA                         # every kind but ipo, newest first
idx stockbit actions TLKM --kind dividend
idx stockbit actions BBRI --kind split,bonus,rights --from 2015-01-01
idx stockbit calendar                             # the next 14 days, oldest first
idx stockbit calendar --kind dividend --days 30
idx stockbit calendar --kind ipo,tender --from 2026-01-01 --to 2026-12-31

One structured row per action, with the dates and terms and no announcement text (schema idx.stockbit.actions.v1). Kinds: dividend, stock_dividend, bonus, rights, warrant, split, reverse_split, rups (alias meeting), public_expose (expose), ipo, tender_offer (tender), or all. Each kind is one request, a second apart, cached for 6 hours; a kind that fails leaves a warning and the others still show. The pause holds across every command and MCP tool call on the same session in one idx process, so parallel tool calls queue a second apart too.

  • A row's date is its cum date for dividends, bonuses, rights and splits (the ex date when there is none), else the event's first day: a warrant's first trading day, the meeting or expose day, an IPO's listing day, a tender offer's opening day.
  • stockbit actions reads a stock's whole history: BBCA's cash dividends go back to 2009, its splits to 2004. --from/--to narrow it. The default is every kind but ipo.
  • stockbit calendar reads the market-wide lists and keeps the rows dated in the range (at most 366 days; default today and the next 14), plus rights trading windows, tender offers and IPO offerings still open in it. The default is every kind but warrant: most market-wide warrant rows are structured warrants, listed under their own codes (ASIIZPCN7A), dozens a month.
  • As recorded on 2026-11-03, the market-wide lists reach about a year back and as far ahead as announced, except RUPS and public exposes, whose market lists started on the recording day. The stock dividend and reverse split lists were empty that day; their rows are read by the field names the other entitlement rows use.
  • ratio_old : ratio_new is as published (1 : 5 for a five-for-one split, 655 held : 104 rights). price is the rights or warrant exercise price, the tender offer price, or the final IPO price, IDR per share. Anything Stockbit leaves empty or zero is null.

Without a login, idx stocks actions (IPOT, with the announcement texts) and idx market calendar (the exchange's own calendar) cover similar ground.

Insider and 5 % holder movements

idx stockbit movements BBCA                      # one stock, newest first
idx stockbit movements                           # the whole market
idx stockbit movements --source ksei --action sell
idx stockbit movements --from 2026-10-01 --to 2026-10-31 --pages 5
idx stockbit movements --holder 1000004481       # one holder, by the HOLDER ID column

Reported changes in a holder's stake (schema idx.stockbit.movements.v1), from two kinds of filing that Stockbit shows in one feed:

  • idx: insider disclosures to IDX. Directors, commissioners and other insiders, any size, with the trade price the insider filed.
  • ksei: KSEI's daily changes of holders at 5 % and above. No price is filed; Stockbit shows an estimate, which idx keeps in estimated_price and estimated_value (shown as ~21 in the table), never in price or value.

shares_from, shares_to and shares_change are exact and signed; pct_from, pct_to and pct_change are percent of the company as Stockbit rounds them (an insider's 0.0003 % stays 0.0003). A rights issue the holder did not take up shows as shares_change: 0 with a negative pct_change. --action filters buy, sell, transfer, cross (crossings between accounts: repos, pledges) or corporate-action (rights issues, private placements, tender offers and the like); each row's own action is Stockbit's label, lowercased. --holder takes the digits of a row's holder_id, the same on every row of that holder.

Stockbit serves 50 rows a page; --pages reads up to 10, one request each, a second apart, cached for an hour. more says rows remain. The feed gains rows at the top as filings arrive, so a row repeated across pages is kept once, a page cached before the page above it was read is read again live (rows that moved down past the page break would otherwise be on neither page; when it cannot be read again, a warning says rows may be missing), and a later page that fails keeps the rows already read (with a warning).

These are filings, not signals: a share plan reads as many insiders buying at one price on one day (BBCA's directors on 2026-03-25 at 6,982), crossings name securities firms as holders, and rows appear days after the change. The monthly register of holders above 1 % (idx ownership, no login) is the other view.

Broker summary

idx broker BBCA                           # every broker, once logged in
idx broker BBCA --source stockbit -n 0    # Stockbit or nothing, every row in the table
idx broker BBRI --from 2026-11-02 --to 2026-11-06 --investor foreign
idx broker BBCA --source ipot             # IPOT's top 10, no login

idx broker reads Stockbit's broker summary by itself once a working session is stored (what idx stockbit status calls logged in): --source auto, the default, takes Stockbit then and IPOT otherwise, and --source stockbit fails, naming idx stockbit login, when no session works. The schema stays IPOT's, idx.broker.v1:

  • idx asks for gross figures: each broker's own buying on the buy side and its own selling on the sell side, as IPOT publishes them. Stockbit's net mode lists each broker once, on the side where it was net; idx does not use it and refuses a reply in those figures.
  • Every broker is listed. BBCA had 59 buyers and 67 sellers on 2026-11-06, and 92 a side over the year to that day. buyer_count and seller_count are Stockbit's counts; buyers_complete and sellers_complete say whether each list holds them all. A cut list keeps its totals null, and the table says Top buyers (5 of 59).
  • Stockbit ranks a side by value; idx orders it by lots, as IPOT ranks, so rank means the same. Ordered so, Stockbit's first ten were IPOT's top 10 for 2026-11-06 and for 2026-11-02 to 2026-11-06: the same brokers in the same order, with the same lots and values at IPOT's precision. Classes are Stockbit's own labels (Asing, Lokal, Pemerintah), which can differ from IPOT's: HD is foreign on Stockbit and local on IPOT.
  • Figures are exact (rounded: false), and odd lots count as fractions of a lot (YU bought 108,898.5 lots that day). Totals are sums over the full lists: both sides came to 1,035,652,066,452 IDR. Stockbit's own totals are the buy side's at about eight significant digits and are not used.
  • The foreign net comes from a second read, of foreign investors over the same range and board: their buying less their selling, 218,988,301,100 IDR for BBCA on 2026-11-06 (IPOT's F. NVal: 219.0 B). With --board regular it is the regular board's, which IPOT does not publish. A day without trades needs no second read; when the second read fails the net is null with a warning.
  • A range is one read, aggregated by Stockbit, so no session is rebuilt one by one as IPOT's foreign net over a range is.
  • Domestic investors and the cash and negotiated boards have not been recorded yet, so idx does not ask Stockbit for them: auto takes IPOT, with a note, and --source stockbit is a usage error.
  • Under auto, a ticker Stockbit fails for (its CDN refusing this network, a session Stockbit ended) is read from IPOT with a warning; after a failure no other read could get past, the rest of the call is read from IPOT too. Each summary's source says which answered.
  • Reads are paced a second apart like the other Stockbit lists: one or two per ticker, plus one per weekday without trades while looking for the latest session. A range that ended before today is cached for a week, one that reaches today for five minutes.

Without a working session, or with --source ipot, the summary is IPOT's top 10 as before; under auto a note then says a login lists every broker, when a list reached IPOT's ten rows.

MCP

The MCP server exposes five Stockbit tools alongside the rest:

  • idx_stockbit_status: can the stored session work now (logged_in; expired, renewable and reason say why not, or why idx cannot renew it), and when do the tokens expire (never the token values). Call it before the others.
  • idx_orderbook: the live depth ladder and foreign flow for one ticker.
  • idx_stockbit_actions: one stock's corporate actions (symbol), or the market calendar without one; kind, from, to, days as on the CLI.
  • idx_stockbit_movements: insider and 5 % holder movements; symbol, from, to, holder, action, source, pages as on the CLI.
  • idx_stockbit_login: starts a sign-in. method: page returns at once with a URL on 127.0.0.1 (and the ssh -N -L forward when the server is reached over SSH) for the user to open in their own browser, as with --page; the agent then polls idx_stockbit_status, whose sign_in shows waiting, signed_in or failed. method: browser opens a browser on the machine running the server and waits, which only suits a local stdio server the user sits at. A session that already works, and that idx can renew, is reported without opening anything (force signs in anyway). The tool never asks for, and agents should never ask the user for, a password, a cookie or a token in the chat. Prefer telling the user to run idx stockbit login in a terminal themselves.

idx_broker reads the same session: its source (auto, ipot, stockbit) works as --source does on the CLI, so an agent gets every broker once the user is logged in.

To serve realtime quotes/history over MCP, run the server with the Stockbit provider: idx --provider stockbit mcp. For Stockbit candles with the free providers for everything else, register it with -e IDX_HISTORY_PROVIDER=stockbit instead.

Where the session lives

The session file holds the rotating tokens and is written with 0600 permissions on Unix. By default it sits beside the default config file (e.g. %APPDATA%\idx\stockbit-session.json on Windows, ~/.config/idx/stockbit-session.json on Linux), also when --config points elsewhere. Move it with:

  • stockbit.session in config.toml;
  • IDX_STOCKBIT_SESSION=/path/to/session.json.

For an unattended deployment (a server, the web worker) seed the tokens through the environment instead of logging in interactively:

  • IDX_STOCKBIT_REFRESH_TOKEN — the refresh token (required);
  • IDX_STOCKBIT_ACCESS_TOKEN — an access token to start with (optional).

The file always wins over the env seeds; the seeds only fill an empty session.

One refresher per session

Stockbit's POST /login/refresh rotates the refresh token: every refresh mints a new one and retires the token it was given. So a single session must have exactly one process refreshing it. If you point two tools (say idx and another bot) at the same Stockbit session, they will retire each other's token and both get logged out. Give each its own idx stockbit login / account, or let one own the refresh and feed the other its current token.

Inside idx, refreshes are serialised so that one session does have one refresher: a process keeps one session store per session file (the CLI handler, the --provider stockbit provider, every MCP tool call and every MCP HTTP session share it; an MCP server takes it once when it starts and holds it until it stops), and a refresh also takes a lock file beside the session (stockbit-session.lock) so a CLI command and a running MCP server do not refresh at the same moment. Inside both locks the file is read again: a pair another holder just stored is used, not refreshed a second time. A lock older than a minute counts as left behind by a crashed process and is taken over. A refresh token Stockbit refuses is not presented again by the same process; idx stockbit login is the way back.

What idx talks to

All on https://exodus.stockbit.com, with Authorization: Bearer <access JWT> and browser-shaped origin/referer headers:

endpointused for
GET /company-price-feed/v2/orderbook/companies/{SYM}quote (OHLC, volume, value, foreign flow only while has_foreign_bs is true) and the full depth ladder
GET /charts/{SYM}/daily?timeframe=..&chart_type=PRICE_CHART_TYPE_CANDLEOHLCV candles
GET /corpaction/{type}[?symbol={SYM}]corporate actions, one kind per request: dividend, stock_dividend, bonus, rightissue, warrant, stocksplit, reversesplit, rups, pubex, ipo, tenderoffer
GET /insider/company/majorholder?page=N[&symbol=][&date_start=][&date_end=][&insider=][&action_type=][&source_type=]insider and 5 % holder movements, 50 rows a page
GET /marketdetectors/{SYM}?transaction_type=TRANSACTION_TYPE_GROSS&market_board=MARKET_BOARD_ALL|_REGULER&investor_type=INVESTOR_TYPE_ALL|_FOREIGN&from=..&to=..&limit=1000the broker summary: every broker that bought and sold over the range (idx broker)
GET /company-price-feed/market-time/sessionchecks a session before idx stockbit login stores it (never from the cache)
POST /login/refreshrotate the session (bearer = refresh token, empty body)

The same API places orders and moves cash, so idx keeps an allowlist of the read-only paths above and refuses any other URL before a token is read. A 401, or a 403 with a JSON body, means Stockbit refused the access token: idx refreshes once and retries once. A 403 with an HTML body is Stockbit's CDN (CloudFront) refusing this client or network; a refresh cannot change that, so idx reports it and does not retry. The same goes for POST /login/refresh: only a 401, or a 403 with a JSON body, counts as a refused refresh token there; an HTML 403 is reported as the block, and the token stays usable once it lifts.

Indices use Stockbit's own code (IHSG, LQ45), mapped from idx's caret form (^JKSE, ^JKLQ45). The token is never written to a log, an error message or a cache key.

From docs/stockbit.md on the main branch, version 0.8.2. Back to the top