Trackers API
Observational market trackers in one standardized envelope: institution rankings, hedge fund returns, Reddit positioning, and sentiment leaderboards.
Overview
Trackers are observational data products. Each tracker has its own data source and refresh cadence; underneath, they all return the same standardized envelope, so a builder writes one renderer per viewType and gets every current and future SentiSense tracker for free.
Currently live trackers:
| Tracker id | viewType | accessTier | What it shows |
|---|---|---|---|
institution-concentration |
table | free | 13F filers ranked by top-10 concentration (the most conviction-driven portfolios) |
institution-aum |
table | free | Largest 13F filers by total disclosed long-equity AUM |
hedge-fund-reported-returns |
table | pro | Net-of-fee annual returns large hedge funds publish, with a citation per number |
reddit-picks |
table | free | Tradeable stocks finance-Reddit turned bullish on, scored vs SPY |
media-darlings |
table | free | Stocks ranked by how bullish or bearish the curated financial press is on them |
sentiment-leaderboard |
table | free | Most bullish and most bearish stocks by pure sentiment polarity (tone, not the SentiSense Score), with a minimum-mention confidence floor and a driving story per row |
sentiment-movers |
table | free | Biggest 7-day shifts in tone, improving and deteriorating, with a minimum-mention confidence floor |
trending-products |
table | free | Products and services ranked by mention volume and week-over-week growth |
market-heatmap |
heatmap | pro | The whole board in one call: every stock in an index sized by market cap and coloured by the day's move, with sectors, sector Market Mood rollups, and sentiment, Score and options overlays per tile |
The two sentiment trackers rank individual stocks by tone. For the sector-level and whole-universe cuts of that same tone metric, use /api/v1/sentiment/sectors (the 11 GICS sectors versus the market's own tone) and /api/v1/sentiment/breadth (the bullish/neutral/bearish share of the roughly 1,000 covered stocks) in the Market Mood API.
More tracker types ship over time; the discovery endpoint is the source of truth for what's live.
Access: an API key is required on every tracker endpoint. Each tracker declares an accessTier of "free" or "pro" in the discovery listing. A free tracker returns the full snapshot to everyone. A pro tracker returns a preview to FREE callers (isPreview: true, previewReason: "PRO_REQUIRED", with totalCount carrying the full row count) and the full snapshot to PRO, in one of two shapes:
- Fewer rows. A ranked leaderboard returns its top N rows;
hedge-fund-reported-returnsworks this way. - Fewer columns. A tracker whose value is that the dataset is complete returns every row and withholds the proprietary columns instead, naming them in
data.meta.previewWithheld.market-heatmapworks this way: every tile on every tier, with the sentiment and options overlays reserved for PRO.
Methodology: every tracker carries a methodologyAnchor field in the discovery listing that points at its section on sentisense.ai/methodology (e.g. #institution-rankings for the concentration and AUM rankings). An empty methodologyAnchor means the tracker has no dedicated section yet; link the page itself.
GET /api/v1/trackers
Discovery endpoint. Returns every publicly-visible tracker the platform knows about, with the metadata you need to render a hub or build a navigation menu.
Response
{
"trackers": [
{
"trackerId": "institution-concentration",
"displayName": "Most concentrated institutions",
"category": "institutional",
"description": "13F filers ranked by the share of their disclosed book held in their top 10 positions.",
"viewType": "table",
"accessTier": "free",
"methodologyAnchor": "#institution-rankings",
"refreshIntervalSeconds": 86400,
"canonicalUrl": "/api/v1/trackers/institution-concentration"
}
]
}
| Field | Type | Notes |
|---|---|---|
trackerId |
string | Slug for the detail endpoint |
displayName |
string | Hub-card title; also surfaced inside the snapshot envelope |
category |
string | Coarse grouping for hub filtering ("institutional", etc.) |
description |
string | One sentence; suitable for a hub card subtitle |
viewType |
string | Renderer hint, "table" or "heatmap". New viewType values are documented here when a tracker using them ships. |
accessTier |
string | "free" or "pro". pro trackers return a preview to FREE callers, either fewer rows or fewer columns (see Access above); free trackers return the full snapshot to everyone. |
methodologyAnchor |
string | Fragment on /methodology for the tracker (link out without per-tracker logic) |
refreshIntervalSeconds |
integer | Expected snapshot refresh cadence in seconds |
canonicalUrl |
string | Detail endpoint path |
GET /api/v1/trackers/{trackerId}
Standardized snapshot envelope for one tracker.
Path parameter
| Parameter | Description |
|---|---|
trackerId |
Slug from the discovery listing, e.g. institution-concentration. Unknown ids return 404 unknown_tracker. |
Query parameters (optional, provider-specific)
| Parameter | Description |
|---|---|
scope |
Which universe a scoped tracker should return. market-heatmap accepts sp500 (default), nasdaq100 or popular; any other value returns 400 invalid_scope listing the valid ones. Ignored by trackers that are not scoped. |
category |
For the institution-ranking trackers (concentration, aum): overrides the default filer-category filter. Pass ?category= (empty) to open the universe across all categories instead of the default hedge-fund filter. |
Response shape (the envelope)
Every tracker returns the same wire shape:
{
"isPreview": false,
"previewReason": null,
"data": {
"trackerId": "institution-concentration",
"schemaVersion": "1.0",
"displayName": "Most concentrated institutions",
"description": "13F filers ranked by the share of their disclosed book held in their top 10 positions.",
"viewType": "table",
"asOf": "2026Q1",
"generatedBy": "InstitutionRankingsService",
"headline": [
{ "label": "Institutions ranked", "value": 28, "unit": "filers" }
],
"rows": [
{
"rank": 1,
"rowId": "0001067983",
"name": "Berkshire Hathaway",
"category": "HEDGE_FUND",
"url": "/institutions/Berkshire-Hathaway",
"metrics": [
{ "label": "Top-10 concentration", "value": 91.2, "unit": "percent" },
{ "label": "Disclosed AUM", "value": 264000000000, "unit": "usd" },
{ "label": "Holdings", "value": 40, "unit": "count" }
]
}
]
}
}
| Envelope field | Type | Notes |
|---|---|---|
isPreview |
boolean | true only when a pro-tier tracker is served to a non-PRO caller. Something is withheld: either the rows past the free limit, or the proprietary columns named in data.meta.previewWithheld. false for free trackers and for PRO callers. |
previewReason |
"PRO_REQUIRED" or null |
Only set when isPreview is true. |
totalCount |
integer | The full row count, before any truncation. Present on every response, so you never have to know your tier to size a board. On a column-gated tracker it therefore equals the number of rows you were served. null on a tracker whose viewType carries no rows. |
upgrade |
object | Present only when isPreview is true. Carries plan, message, price, url and relay: surface message and url to your user in one line, then continue with the preview data. |
data |
object | The standardized TrackerSnapshot (below). |
| Snapshot field | Type | Notes |
|---|---|---|
trackerId |
string | Echoes the request path. |
schemaVersion |
string | "1.0" today. Bumped when the envelope shape changes incompatibly. |
displayName |
string | Same as the discovery listing. Surfaced here so renderers don't need to cross-reference. |
description |
string | Same as the discovery listing. |
viewType |
string | The renderer hint, "table" or "heatmap"; dispatch on this to pick a renderer. |
asOf |
string | Free-form 'data as of' label, typically a quarter ("2026Q1") or ISO date for daily trackers. |
headline |
array | Top-of-page stat tiles. Each item is { label, value, unit?, asOf?, methodologyNote?, trend? }. |
rows |
array | One row per entity. Each row carries its per-cell metrics[] (label + value + unit) so a generic renderer can derive column headers from rows[0].metrics. A wide dataset tracker such as market-heatmap also writes its columns as named fields directly on the row (rows[].marketCap), which is cheaper to read and far smaller on the wire than repeating a label and unit on every one of 500 rows. |
Reading the table-row metrics
Each rows[].metrics[] entry is a { label, value, unit } tuple. Column headers come from the labels on the first row; the order is stable across rows in a single response.
For the institution-ranking trackers, the metric set per row is:
| Tracker | Metrics (in order) |
|---|---|
institution-concentration |
Top-10 concentration, Disclosed AUM, Holdings |
institution-aum |
Disclosed AUM, Holdings |
unit is one of "percent", "usd", "count"; a renderer can use it to format value without per-tracker logic.
The market-heatmap tracker
One call returns a whole index as tiles, which is what makes it renderable as a self-contained
treemap with no follow-up requests. GET /api/v1/trackers/market-heatmap?scope=sp500.
Every caller gets every tile. Rows sort by market cap descending, and neither tier is
row-limited: a FREE key, a PRO key and the web app all receive the whole index, the fullheadline, and the full meta including the eleven sector rollups. This tracker gates columns
instead. A non-PRO response carries isPreview: true, previewReason: "PRO_REQUIRED" andmeta.previewWithheld, and omits the proprietary overlay fields from every row:
| Withheld layer | Fields removed for a non-PRO caller |
|---|---|
sentiment |
sentiment7d, sentimentChange7d, sentisenseScore, mentionsZ |
options |
optionsInterestScore |
meta.previewWithheld is a list of those layer names, and it lists only layers this snapshot
actually has: if the options layer did not run today it is absent from meta.layers and absent
from previewWithheld too, because a PRO caller would see nothing for it either. The field is
absent entirely on a PRO response. Everything else is open on every tier: ticker, name, sector,
industry, market cap, the delayed quote, the sector rollups and the Market Mood headline.
A withheld field is simply not present on the row, exactly like a ticker with no reading. So readmeta.previewWithheld, not an absent field, when deciding whether to show a locked control: an
absent sentisenseScore means "no reading for this ticker" on a PRO response and "upgrade" on a
FREE one.
Each row carries these named fields alongside the standard rank, rowId, name (company
name), category (the sector) and url:
| Field | Notes |
|---|---|
ticker |
The tile's symbol |
sector |
Canonical GICS-11 name, or Unclassified. Never a provider variant, so the eleven sectors always bucket cleanly |
industry |
Vendor industry, finer than the sector |
marketCap |
USD. The tile's size |
price, previousClose, changePercent, volume |
Delayed quote. changePercent is the tile's colour |
priceAsOf |
When the quote is from. Absent outside regular hours, where the vendor dates nothing |
sentiment7d, sentimentChange7d |
Seven-day mean tone in [-1, 1], and its change over a week |
sentisenseScore |
Today's SentiSense Score |
mentionsZ |
Today's mention volume in standard deviations of its 30-day baseline |
optionsInterestScore |
Options interest, for tickers with options coverage |
Within the layers you are served, a field that is absent has no reading for that tile. It is
never a zero, and a zero is never a missing value. A measured 0.0 is always written, including a genuinely flat changePercent
and a genuinely neutral Score. Whether an overlay ran at all is answered by meta.layers, not by
the rows: a layer that failed or is not shipped is absent from that object entirely, solayers.sentiment present with sentiment7d missing on a row means "we looked and there is
nothing for this ticker".
meta carries the board-level picture: scope, universeSize, tileCount, missingPrice[]
(tickers dropped for having no quote, listed rather than silently omitted), breadthUp,breadthDown, capWeightedChangePct, equalWeightedChangePct, marketMoodScore,marketMoodPhase, previewWithheld (non-PRO responses only, see above), the layers stamps
above, and sectors[] with one rollup per sector
(sector, marketMoodScore, marketMoodPhase, marketMoodWeeklyChange, count,capWeightedChangePct, equalWeightedChangePct, capUsd, up, down). Sectors with no Market
Mood reading carry nulls there, not zeros.
Snapshots are rewritten every 15 minutes during the US regular session and hourly outside it.
Prices are delayed by about 15 minutes (meta.layers.prices.delayMinutes), so do not present the
board as real time. Before the first write of a scope the answer is 404 no_snapshot, which is a
warm-up state rather than an outage: retry in 15 minutes.
Error responses
| Status | Body | Cause |
|---|---|---|
404 unknown_tracker |
{ "error": "unknown_tracker", "message": "No registered tracker with id '...'" } |
Slug not in the registry |
404 no_snapshot |
{ "error": "no_snapshot", "message": "Tracker '...' has no snapshot available" } |
Tracker registered but hasn't produced data yet |
400 invalid_scope |
{ "error": "invalid_scope", "message": "Unknown scope '...'. Valid values: ..." } |
A scope value the tracker does not serve. It is never silently coerced to the default. |
503 tracker_unavailable |
{ "error": "tracker_unavailable", "message": "Tracker '...' is temporarily unavailable" } |
The provider threw; retry with backoff |
SDK usage
Python:
from sentisense import SentiSenseClient
client = SentiSenseClient(api_key="...")
for tracker in client.list_trackers().trackers:
print(tracker.trackerId, tracker.displayName)
snapshot = client.get_tracker("institution-concentration")
print(snapshot.data.displayName, snapshot.data.asOf)
for row in snapshot.data.rows[:5]:
print(row.rank, row.name, row.metrics[0].value) # first metric = top-10 concentration
Node:
import { SentiSense } from "sentisense";
const client = new SentiSense({ apiKey: "..." });
const { trackers } = await client.trackers.list();
for (const t of trackers) console.log(t.trackerId, t.displayName);
const snap = await client.trackers.get("institution-concentration");
console.log(snap.data.displayName, snap.data.asOf);
for (const row of (snap.data.rows ?? []).slice(0, 5)) {
console.log(row.rank, row.name, row.metrics[0].value);
}
Why this shape
Trackers are observational dashboards (see the methodology page for how each one is built). The standardized envelope plus the viewType taxonomy means:
- One renderer per
viewTypein your code, and you cover every tracker we ship. - Adding a new tracker is additive: no breaking changes to the contract, no parallel endpoints, no per-tracker shape work for consumers.
- Discovery (
GET /api/v1/trackers) returns enough metadata to render a navigation menu without hitting any tracker's detail endpoint.
This is the same convention used by CDC NNDSS (US disease surveillance), FRED (economic indicators), World Bank, OECD, and EIA dashboards.
Try It
Test endpoints directly from your browser. Paste your API key once: it's saved locally and shared across all widgets. Get a free key
GET/api/v1/trackers
List every publicly-visible tracker
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/trackers"GET/api/v1/trackers/{trackerId}
Standardized snapshot envelope for one tracker
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/trackers/sentiment-leaderboard"