API Reference

ETFs API

ETF discovery, holdings composition, and holdings-weighted aggregate views (analyst consensus, insider activity, sentiment). Read fund-level signals derived from each constituent's per-stock data, weighted by allocation.

Base: /api/v1/etfsFree (PRO unlocks per-holding contributors on aggregate endpoints)6 endpoints

Overview

The ETFs API exposes fund-level data for the exchange-traded funds SentiSense tracks. Two flavors of endpoints:

  1. Composition data (/, /{ticker}/holdings) — issuer-level facts: which ETFs exist, what each one holds, when the composition was last refreshed.
  2. Holdings-weighted aggregates (/{ticker}/aggregates/...) — synthesized fund-level views derived from each constituent's per-stock data, weighted by allocation. Three rollups today: analyst consensus, insider activity, and SentiSense sentiment.

Why aggregates matter: funds aren't rated by sell-side analysts, don't have insiders of their own, and may not get many direct news mentions. But the companies inside a fund do. The aggregate endpoints surface "what the underlying constituents are signaling, weighted by how much of the fund they represent" — a view that's genuinely useful for ETF research and isn't published anywhere else.

Coverage transparency: every aggregate response carries a coverage block (holdingsCount, holdingsCovered, weightCovered, partial, totalKnownHoldings) so consumers can interpret the headline number with the right confidence. When fewer than ~30% of fund AUM has the underlying data (e.g. foreign-listed funds like VEA/VWO where most holdings have no per-stock coverage), the endpoint returns 404 rather than publish a misleading aggregate.

Beta notice: ETF coverage is in beta as of 2026-05-15. Starting with a limited set of widely-traded funds (SPY, QQQ, IWM, VOO, VTI, the major SPDR sector ETFs, etc.). Expect coverage and aggregate freshness to improve over time.

Access: all endpoints require an API key. The composition endpoints (list, holdings) are on the Free tier and the list endpoint does not consume monthly quota. The aggregate endpoints follow the standard PRO-with-FREE-preview pattern (FREE sees the headline + coverage block, PRO unlocks per-holding contributors).


GET /

Returns every ETF SentiSense tracks. Sorted by ticker for stable client-side caching.

Authentication: API key required (no quota cost) | Parameters: None

Example Request:

curl "https://app.sentisense.ai/api/v1/etfs"

Response: array of ETF info objects.

Field Type Description
ticker string Fund ticker (e.g. QQQ)
name string Official fund name
kbEntityId string Knowledge base entity ID (e.g. kb/etf/22)
urlSlug string URL-friendly slug for the fund detail page
issuer string Fund issuer (e.g. Invesco, Vanguard, iShares)
trackedIndex string | null Reference index or strategy description
assetClass string | null One of Equity, Bond, Commodity, etc.

GET /{ticker}/holdings

Returns the full composition of an ETF: which stocks it holds, each holding's weight, and freshness metadata.

Authentication: API key required

Parameters:

Parameter Type Required Default Description
ticker path Yes - ETF ticker (e.g. QQQ)

Example Request:

curl "https://app.sentisense.ai/api/v1/etfs/QQQ/holdings"

Response object:

Field Type Description
ticker string Fund ticker
issuer string Fund issuer
issuerEndpoint string | null Source URL the composition was fetched from
asOfDate string Composition snapshot date from the issuer (ISO date YYYY-MM-DD)
fetchedAt long | null When SentiSense refreshed the composition (epoch seconds)
nextRefreshDue string When the composition is scheduled to be refreshed next (ISO date YYYY-MM-DD)
totalHoldings int Number of holdings in the visible composition
holdings array Per-holding rows (see below)
partial boolean | null true when the composition is a top-N view rather than the full fund
totalKnownHoldings int | null Issuer's reported total holdings when partial=true

Holding object:

Field Type Description
ticker string Constituent ticker
name string Constituent company name
weightPct decimal Holding weight in the fund, expressed as a percentage (0–100)
firstSeen string | null First date this holding appeared in the composition (ISO date YYYY-MM-DD)

Returns 404 if the ETF isn't tracked or no holdings file is available (commodity-only funds like GLD have no equity holdings).


GET /{ticker}/quote

Returns a single-call aggregate snapshot for the ETF detail page: live price + today's OHLC + 52-week range + trailing-12-month dividend yield + ETF fundamentals (AUM, expense ratio, NAV, inception date). Peer of GET /stocks/{ticker}/quote for fund tickers.

Authentication: API key required | Rate limit: standard quota

Parameters:

Parameter Type Required Default Description
ticker path Yes - ETF ticker (e.g. VTI)

Example Request:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/etfs/VTI/quote"

Response object:

Field Type Description
ticker string ETF ticker
currentPrice number | null Regular-session price. During RTH (09:30 to 16:00 ET): live last trade. Otherwise: most recent regular-session close.
change number | null currentPrice change vs previousClose
changePercent number | null currentPrice change percentage vs previousClose
volume number | null Volume for the current session
open number | null Opening price for the current session
dayHigh number | null Intraday high
dayLow number | null Intraday low
previousClose number | null Previous session close
week52High number | null 52-week high
week52Low number | null 52-week low
dividendYield number | null Trailing-12-month distribution yield (decimal, e.g. 0.0104 for 1.04%)
aum number | null Assets under management, USD. ETF analogue of marketCap on the stock quote.
expenseRatio number | null Annual fund expense ratio (decimal, e.g. 0.0003 for 0.03%)
nav number | null Net asset value per share, USD
inceptionDate string | null Fund inception date (ISO YYYY-MM-DD)
timestamp number | null Quote timestamp (epoch milliseconds)
extendedHours object | null Extended-hours view (pre-market or after-hours). Absent during RTH, overnight, and weekends. Shape: { session, price, change, changePercent }.

All fields except ticker are nullable. Render "--" or hide the row when a field is absent.

Example response:

{
  "ticker": "VTI",
  "currentPrice": 363.74,
  "change": -4.66,
  "changePercent": -1.27,
  "volume": 3500000,
  "open": 364.53,
  "dayHigh": 364.93,
  "dayLow": 362.28,
  "previousClose": 367.40,
  "week52High": 368.25,
  "week52Low": 283.00,
  "dividendYield": 0.0104,
  "aum": 2200000000000,
  "expenseRatio": 0.0003,
  "nav": 362.68,
  "inceptionDate": "2001-05-24",
  "timestamp": 1747535400000
}

Stock tickers: This endpoint is ETF-only. Calling it with a stock ticker (e.g. AAPL) returns 400 ticker_is_not_etf with a pointer to GET /stocks/{ticker}/quote which returns market cap, P/E, and EPS instead of AUM and expense ratio.

Rate-limit note: Cached for 15 seconds server-side. Each call still counts toward your monthly quota.


GET /{ticker}/aggregates/analyst

Returns a holdings-weighted analyst consensus for an ETF, derived from each covered constituent's per-stock analyst coverage. Math: weight × per-stock upside, renormalized to the covered subset.

Authentication: API key required. Returns the full response (including topContributors) to every API caller; PRO and FREE differ only in per-tier rate limits and monthly quota.

Parameters:

Parameter Type Required Default Description
ticker path Yes - ETF ticker

Example Request:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/etfs/QQQ/aggregates/analyst"
from sentisense import SentiSenseClient

client = SentiSenseClient(api_key="ss_live_YOUR_KEY")
result = client.get_etf_analyst_aggregate("QQQ")
agg = result["data"]
print(f"QQQ weighted 12mo upside: {agg['weightedConsensus']['upsidePercent']}%")
print(f"Coverage: {agg['coverage']['holdingsCovered']} of {agg['coverage']['holdingsCount']} holdings"
      f" ({agg['coverage']['weightCovered']}% AUM)")

Response Schema:

Field Type Description
isPreview boolean true when the caller is on the FREE tier
previewReason string "PRO_REQUIRED" or null
data object Aggregate (see below)

Aggregate object:

Field Type Description
ticker string ETF ticker
asOfDate string Composition snapshot date the rollup was computed against (ISO date YYYY-MM-DD)
computedAt long Epoch seconds when this rollup was computed
coverage object Coverage block (see below)
weightedConsensus object Weighted consensus (see below)
topContributors array Top 10 contributors by absolute contribution to the weighted upside

Coverage object (same shape on all three aggregate endpoints):

Field Type Description
holdingsCount int Holdings present in holdings.json
holdingsCovered int Holdings that had per-stock data and were included in the aggregate
weightCovered decimal Sum of weights (0–100) for the covered holdings
partial boolean | null true when the underlying composition is a top-N view
totalKnownHoldings int | null Issuer's true holdings count when partial=true

Weighted consensus object:

Field Type Description
upsidePercent decimal Weighted 12-month upside as a percentage (e.g. 12.4 = +12.4%)
consensusLabel string Most-weighted bucket: BUY / HOLD / SELL
distribution object Fractions of covered AUM in each bucket: { "BUY": 0.62, "HOLD": 0.31, "SELL": 0.07 }
totalAnalysts int Sum of analyst counts across covered holdings

Contributor object:

Field Type Description
ticker string Constituent ticker
weightPct decimal Holding weight in the fund (0–100)
upsidePercent decimal Per-stock analyst upside
consensusLabel string Per-stock consensus bucket
contributionPp decimal Signed contribution to the fund's weighted upside, in percentage points

Returns 404 with {error, reason, message} when the ticker isn't an ETF or when coverage is too low to publish a defensible aggregate (typically foreign-listed funds where most holdings lack per-stock data).


GET /{ticker}/aggregates/insider

Returns a holdings-weighted insider activity aggregate for an ETF: net dollar flow, buy/sell split, and per-holding contributors over a configurable trailing window.

Authentication: API key required. Returns the full response (including topContributors) to every API caller; PRO and FREE differ only in per-tier rate limits and monthly quota.

Parameters:

Parameter Type Required Default Description
ticker path Yes - ETF ticker
lookbackDays int No 30 Trailing days of insider trades to include (typical values: 30, 90)

Example Request:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/etfs/ARKK/aggregates/insider?lookbackDays=90"
result = client.get_etf_insider_aggregate("ARKK", lookback_days=90)
flow = result["data"]["weightedNetFlow"]
print(f"Net insider flow: ${flow['netDollars']:,} ({flow['buyTradeCount']} buys, {flow['sellTradeCount']} sells)")

Aggregate object:

Field Type Description
ticker string ETF ticker
asOfDate string Composition snapshot date (ISO date YYYY-MM-DD)
computedAt long Epoch seconds when this rollup was computed
lookbackDays int Window included in the trade aggregation
coverage object Coverage block (same shape as analyst aggregate)
weightedNetFlow object Aggregated flow (see below)
topContributors array Top 10 contributors by absolute weighted-net-dollar contribution

Weighted net flow object:

Field Type Description
netDollars long Weighted net dollar flow (buys − sells). Negative = net selling.
buyDollars long Weighted gross buy dollars
sellDollars long Weighted gross sell dollars
buyTradeCount long Total buy trades across covered holdings (unweighted, for context)
sellTradeCount long Total sell trades across covered holdings (unweighted, for context)
distinctInsiderCount int Distinct insiders across covered holdings

Contributor object:

Field Type Description
ticker string Constituent ticker
weightPct decimal Holding weight in the fund (0–100)
netDollars long Per-stock net insider flow over the window (signed)
weightedNetDollars long Signed contribution to the fund's weighted net flow
tradeCount int Per-stock trade count over the window

Returns 404 when no holdings have insider activity over the window or coverage is insufficient.


GET /{ticker}/aggregates/sentiment

Beta as of 2026-05-15. The constituent-weighted score is precomputed daily for a growing set of widely-traded funds (SPY, QQQ, VOO, VTI, IWM, major SPDR sector funds, and more) and is expanding as we ingest more ETF data. Returns 404 for funds outside the current coverage window.

Returns two complementary SentiSense Score readings for an ETF side-by-side:

  • Constituent-Weighted — daily-precomputed weighted average across the fund's constituents (the new view).
  • Direct — score derived from mentions of the fund's own ticker (the existing per-ETF sentisense_score).

These can diverge meaningfully — bull market chatter about a fund itself may not match the sentiment around its individual holdings, and the gap can be a signal.

Authentication: API key required. Response uses the standard {isPreview, previewReason, data} wrapper; the headline is the same for FREE and PRO (no per-holding contributors are included in v1).

Parameters:

Parameter Type Required Default Description
ticker path Yes - ETF ticker

Example Request:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/etfs/QQQ/aggregates/sentiment"
result = client.get_etf_sentiment_aggregate("QQQ")
agg = result["data"]
print(f"Constituent-weighted: {agg['constituentsWeighted']['sentiSenseScore']} ({agg['constituentsWeighted']['scoreLabel']})")
if agg.get("direct"):
    print(f"Direct (mentions of QQQ itself): {agg['direct']['sentiSenseScore']} ({agg['direct']['scoreLabel']})")

Aggregate object:

Field Type Description
ticker string ETF ticker
asOfDate string | null Composition snapshot date the weighted view was computed against (ISO date YYYY-MM-DD)
computedAt long Epoch seconds when this aggregate was assembled
coverage object Coverage block (same shape as analyst aggregate)
constituentsWeighted object Weighted reading (see below)
direct object | null Direct reading; null for low-mention funds with no daily score

Reading object (used for both constituentsWeighted and direct):

Field Type Description
sentiSenseScore decimal SentiSense Score reading (signed; magnitude reflects volume × sentiment)
scoreLabel string Coarse-grained label: BULLISH / NEUTRAL / BEARISH
asOfTimestamp long Epoch seconds when the underlying metric was produced

Returns 404 when the ticker isn't an ETF or the constituent-weighted metric hasn't been produced yet (brand-new ETFs may show 404 until the next daily pipeline run).


Errors

Status Code Description
400 invalid_parameter Invalid ticker or out-of-range lookbackDays
401 api_key_required No API key on a programmatic call to an aggregate endpoint
403 quota_exceeded Monthly request quota for your tier is used up
404 not_found ETF not tracked, no composition data, or insufficient coverage for an aggregate

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/etfs/

List all ETFs tracked by SentiSense

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/"
Enter your API key to send requests

GET/api/v1/etfs/{ticker}/holdings

Composition for an ETF (constituents + weights + freshness)

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/AAPL/holdings"
Enter your API key to send requests

GET/api/v1/etfs/{ticker}/quote

ETF detail-page quote: live price + AUM + expense ratio + NAV + inception

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/AAPL/quote"
Enter your API key to send requests

GET/api/v1/etfs/{ticker}/aggregates/analyst

Holdings-weighted analyst consensus + upside across constituents

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/AAPL/aggregates/analyst"
Enter your API key to send requests

GET/api/v1/etfs/{ticker}/aggregates/insider

Holdings-weighted insider net flow across constituents

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/AAPL/aggregates/insider"
Enter your API key to send requests

GET/api/v1/etfs/{ticker}/aggregates/sentiment

Constituent-weighted SentiSense Score + direct fund-level score side-by-side

Try It
curl -H "X-SentiSense-API-Key: ssk_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/etfs/AAPL/aggregates/sentiment"
Enter your API key to send requests