createSeriesMarkers), in plain JavaScript.
What you will build
A candlestick chart with one marker per thesis: green arrow below the bar forbull, red arrow above the bar for bear, grey circle without a stance. The label reads @author +54%, the author’s live PnL on the token. New theses appear the moment they are posted, deleted or hidden ones disappear.
How it fits together
Your API key must never reach the browser. A tiny relay on your server holds the key, loads the history and keeps one connection to the theses stream. The browser only talks to the relay.GET /v1/theses
The theses of a token, with author, stance, created_at and position.
Theses stream
Live thesis events for up to 200 tokens per connection. Every plan.
Step 1, the relay
Node 22 or newer (globalfetch and WebSocket), plus the ws package for the browser-facing socket: npm install ws. Set CABALSPY_API_KEY to your key with CabalSpy ID enabled.
The relay keeps one upstream connection, subscribes to every token a browser is watching, and forwards each thesis event only to the browsers watching that token.
relay.mjs
Step 2, the chart
Load Lightweight Charts 5.0.8 and give the chart a container and a tooltip element.index.html
Step 3, theses to markers
A marker needs a time, a position, a shape, a color and a text. The time must be a Unix timestamp in seconds, and markers must be sorted by time. Roundcreated_at down to the bar interval so the marker sits on the right candle.
markers.js (part 1)
Step 4, draw, load and go live
Create the chart and the marker plugin, load the history from the relay, then apply live events.thesis adds a marker, thesis.removed deletes one. Hovering a marker shows the thesis body, using the marker id that Lightweight Charts reports as hoveredObjectId.
markers.js (part 2)
textContent keeps the thesis text as plain text: never insert it as HTML.
The same relay with the SDK
The JavaScript SDK (npm install cabalspy-id ws) wraps the stream in ThesesStream: it reconnects on its own, watches the heartbeat and sends your subscriptions again after every reconnect. Replace the upstream part and the history call of the relay with this:
relay-sdk.mjs
/theses answers { data: [...] } and /live sends { event, data } messages in both versions.
Lessons worth knowing
Keep the key on the server. The relay is a few dozen lines and is the only thing that ever sees your API key. Validate the chain and mint it accepts. Load once, then stay live. The history comes from one request, everything after that from the stream. After a relay restart or a dropped browser socket, load the history again to fill the gap. Snap times to your bars. Markers sit on bar times, so roundcreated_at down to your candle interval and keep the marker list sorted.
Only theses from your platform? Open the stream with scope=instance and call /v1/theses with scope=instance (csid.thesesStream({ scope: 'instance' }) and listTheses({ scope: 'instance' }) in the SDK). See Reach.
Reference
Token theses
How theses work, the widget and the API.
Theses stream
Frames, events and limits of the live stream.

