Analyst Ratings API
Wall Street analyst price targets, buy/hold/sell distribution, upgrade and downgrade actions, and forward EPS estimates with earnings surprise history.
Overview
The Analyst Ratings API provides programmatic access to Wall Street analyst coverage data: aggregate price target bands, the buy/hold/sell distribution, recent upgrade and downgrade actions, and forward EPS estimates with earnings surprise history.
Use cases:
- Pull the consensus price target and upside-to-current for any covered ticker
- Track the rate and direction of analyst revisions on a stock or across the market
- Compare actual EPS prints against the consensus estimate (earnings surprise tracking)
- Cross-reference analyst signals with insider trading, institutional flows, and AI insights
- Find out who covers a stock by name, and read back everything an individual analyst has published
Free preview: the price target band (targetLow, targetMean, targetHigh, numberOfAnalysts, consensusLabel) is returned in full to free users, along with the as-of stamp (updatedAt, updatedAtEpoch) so you can always tell how fresh a row is. The buy/hold/sell distribution counts, recommendationMean, and targetMedian are PRO-only. The actions feed is limited to the 3 most recent rows for free, and the estimates endpoint returns 1 quarter + 2 surprises for free.
Access: PRO subscription required for full data. Free and unauthenticated users receive a preview wrapped in {isPreview: true, data: ...}.
Building an AI agent? See How to track analyst ratings and price targets with an API for the consensus-plus-sentiment divergence pattern, with a runnable snippet.
GET /{ticker}/consensus
Returns the aggregate Wall Street consensus for a ticker: price target band, number of covering analysts, upside-to-current, and buy/hold/sell recommendation counts.
Authentication: PRO required. Free users receive the price target band in full but no recommendation distribution.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol (e.g. AAPL) |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/consensus"
from sentisense import SentiSenseClient
client = SentiSenseClient(api_key="ss_live_YOUR_KEY")
result = client.get_analyst_consensus("AAPL")
band = result.data
print(f"Target low/mean/high: {band['targetLow']} / {band['targetMean']} / {band['targetHigh']}")
print(f"{band['numberOfAnalysts']} analysts, consensus: {band['consensusLabel']}")
Response Schema:
| Field | Type | Description |
|---|---|---|
isPreview |
boolean | true when the caller is on the FREE tier |
previewReason |
string | "PRO_REQUIRED" or null |
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 | Consensus object (see below) |
Consensus object:
| Field | Type | Description |
|---|---|---|
ticker |
string | Stock ticker |
currentPrice |
decimal | Reference price captured when the snapshot was written (dated by updatedAt), used for the upside calculation. Not the live quote: read /api/v1/stocks/price for that. |
targetLow |
decimal | Lowest analyst price target |
targetMean |
decimal | Mean analyst price target |
targetHigh |
decimal | Highest analyst price target |
targetMedian |
decimal | Median analyst price target (PRO only; null in preview) |
numberOfAnalysts |
int | How many analysts published a price target. A separate group from the five rating fields; never the total for "% of analysts" math. See Reading the recommendation scale. |
upsidePercent |
decimal | Implied upside from current price to mean target (%) |
consensusLabel |
string | Consensus label in enum form, derived from recommendationMean: "STRONG_BUY", "BUY", "HOLD", "SELL", "STRONG_SELL". Returned in full to free users. |
recommendationMean |
decimal | Numeric consensus rating on the 1.0 to 5.0 sell-side scale, where lower is more bullish (1 = Strong Buy, 5 = Strong Sell). PRO only; null in preview. See Reading the recommendation scale. |
strongBuy |
int | Number of Strong Buy ratings (PRO only; 0 in preview) |
buy |
int | Number of Buy ratings (PRO only; 0 in preview) |
hold |
int | Number of Hold ratings (PRO only; 0 in preview) |
sell |
int | Number of Sell ratings (PRO only; 0 in preview) |
strongSell |
int | Number of Strong Sell ratings (PRO only; 0 in preview) |
updatedAt |
string | When the row was last refreshed, as an ISO-8601 UTC instant with a Z suffix (e.g. "2026-09-01T21:04:59Z"), to whole seconds. Returned to free users. |
updatedAtEpoch |
int | The same moment as updatedAt, in epoch seconds (e.g. 1788296699). Prefer this if you would otherwise parse the string. Returned to free users. |
Returns 404 if no analyst coverage data is available for the ticker.
Reading the recommendation scale
recommendationMean is the average of the individual broker recommendations behind the ticker,
on the standard 1.0 to 5.0 sell-side scale. The scale is inverted relative to intuition: lower
is more bullish, so sorts, filters, and screener thresholds built on it need the comparison
flipped. (A few other fields run inverted too, notably the put/call and skew readings on the
options endpoints, where higher reads more bearish; most of the catalog runs the intuitive
direction.)
One reconciliation note: the mean is aggregated from a different underlying analyst set than the
five rating count fields, so do not expect to reproduce it as a weighted average of the counts.
The two usually point the same way but will not match exactly.
A second reconciliation note, about time: the five rating counts are a survey panel of broker
recommendations currently in effect, not a running tally of the actions feed. The aggregator keys
the panel by month and revises it in batches through the month as brokers submit, and we store the
current panel as received on each sweep. So the distribution can change on any sweep (a Hold
becoming a Strong Buy, a Strong Sell disappearing, a broker joining or leaving the panel) with no
matching row on /actions or /activity, because most broker submissions are never published as
a note. The two datasets come from different sources with different coverage and are never derived
from each other. Treat /activity and /{ticker}/actions as the event record (every row carries
the note's actionDate), and the counts as a snapshot of where the panel stood when the sweep ran.
Two mechanical details for anyone diffing daily snapshots: recommendationMean is stored at two
decimal places, and a ticker whose panel fetch returned nothing keeps the stored counts. History
marks that observation with countsObserved: false.
| Value | Meaning |
|---|---|
| 1.0 | Strong Buy |
| 2.0 | Buy |
| 3.0 | Hold |
| 4.0 | Sell |
| 5.0 | Strong Sell |
consensusLabel is derived from it with these cutoffs, so you can reproduce the label yourself
or bucket a mean you computed from your own inputs:
recommendationMean |
consensusLabel |
|---|---|
| <= 1.5 | STRONG_BUY |
| <= 2.5 | BUY |
| <= 3.5 | HOLD |
| <= 4.5 | SELL |
| > 4.5 | STRONG_SELL |
numberOfAnalysts belongs to the price target, not the ratings. The analysts who published
a buy/hold/sell rating are a separate group, counted by the sum of the five rating fields. Not
every analyst publishes both, so the two totals rarely match, and the rating total is usually the
larger. For any "percent of analysts" math, divide by the sum of the five rating fields:
rated = sum(band[k] for k in ("strongBuy", "buy", "hold", "sell", "strongSell"))
bullish_pct = 100 * (band["strongBuy"] + band["buy"]) / rated if rated else None
Dividing by numberOfAnalysts instead mixes the two groups and returns above 100% on many
tickers.
Freshness and update cadence
Analyst data refreshes as a single sweep across the full covered universe (roughly a thousand US
tickers) rather than per ticker, and that sweep runs about once a day but is not anchored to a clock:
it takes roughly 30 minutes to walk the universe and lands at a different hour each day (measured over
2026-08-25 to 2026-09-04: completions between 8:48am and 8:10pm ET, occasionally more than one in a day). Read updatedAt rather than assuming
a schedule. Consensus bands, the recommendation distribution, upgrade and
downgrade actions, and forward estimates all land together in the same pass, so every ticker'supdatedAt moves at once.
Read updatedAt (or updatedAtEpoch) rather than assuming a schedule: it is the authoritative
as-of for the row, it is served on every tier, and it is an absolute UTC instant, so compare it
against now in UTC and not against a local wall clock. The practical consequence for
action-driven workflows is that a rating change published today can be up to about 24 hours old
before it appears in /{ticker}/actions or /activity.
GET /{ticker}/consensus/history
Returns daily observations of the analyst consensus for a ticker. Rows are ordered bysnapshotDate ascending, and limit selects the most recent rows inside the requested window.
Each row is an observation of the upstream current-month recommendation panel plus the target
fields as observed on snapshotDate. countsObserved: false means the panel did not return on
that sweep and the counts are stored values from the last observed panel, or zeros when no panel
has ever been observed. It is not evidence that coverage dropped.
Authentication: PRO required for the full window and all fields. Free users receive the last
30 calendar dates ending at to, starting at the later of from and to minus 29 days;targetMedian, recommendationMean, and the five rating counts are null
in the preview. totalCount still reports the full requested window.
A ticker with history outside the requested window returns 200 with an empty history array.
A tracked ticker with no history at all returns 404 no_coverage.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol, including supported aliases |
from |
string | No | to minus 90 days |
First snapshot date, formatted YYYY-MM-DD |
to |
string | No | Today in America/New_York | Last snapshot date, formatted YYYY-MM-DD |
limit |
int | No | 90 | Most recent rows to return, capped at 366 |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/consensus/history?from=2026-06-01&to=2026-09-01&limit=90"
from sentisense import SentiSenseClient
client = SentiSenseClient(api_key="ss_live_YOUR_KEY")
result = client.get_analyst_consensus_history(
"AAPL", from_date="2026-06-01", to_date="2026-09-01", limit=90
)
for point in result.data["history"]:
print(point["snapshotDate"], point["targetMean"], point["countsObserved"])
Response Schema:
| Field | Type | Description |
|---|---|---|
isPreview |
boolean | true when the caller receives the FREE preview |
previewReason |
string | "PRO_REQUIRED" or null |
upgrade |
object | Present only when isPreview is true; carries plan, message, price, url, and relay |
totalCount |
int | Rows in the full requested window before the limit or FREE clip |
data.ticker |
string | Canonical ticker |
data.from |
string | Effective first date, formatted YYYY-MM-DD |
data.to |
string | Effective last date, formatted YYYY-MM-DD |
data.count |
int | Rows returned in data.history |
data.history |
array | Consensus history points in ascending snapshotDate order |
Consensus history point:
| Field | Type | Description |
|---|---|---|
snapshotDate |
string | America/New_York sweep date, formatted YYYY-MM-DD |
observedAt |
string | Observation time as an ISO-8601 UTC instant with a Z suffix, to whole seconds |
observedAtEpoch |
int | The same moment as observedAt, in epoch seconds |
source |
string | Observation source |
countsObserved |
boolean | false when the five counts were carried from stored values because the panel did not return |
currentPrice |
decimal | Reference price captured with the observation |
targetLow |
decimal | Lowest analyst price target |
targetMean |
decimal | Mean analyst price target |
targetMedian |
decimal | Median analyst price target; null in the FREE preview |
targetHigh |
decimal | Highest analyst price target |
numberOfAnalysts |
int | Analysts in the price-target population |
upsidePercent |
decimal | Implied upside from currentPrice to targetMean, in percent |
recommendationMean |
decimal | Numeric rating on the 1.0 to 5.0 scale; null in the FREE preview |
strongBuy |
int | Strong Buy count; null in the FREE preview |
buy |
int | Buy count; null in the FREE preview |
hold |
int | Hold count; null in the FREE preview |
sell |
int | Sell count; null in the FREE preview |
strongSell |
int | Strong Sell count; null in the FREE preview |
consensusLabel |
string | STRONG_BUY, BUY, HOLD, SELL, or STRONG_SELL |
GET /{ticker}/called-it
Returns recorded large stock moves and the covering firms that revised their price targets in
the same direction beforehand. Moves are newest first by moveEndDate. Each move reports the
full firm counts for revisions with the move, against it, and targets left unchanged. The calls
list contains the same-direction revisions, with analyst names only where the published note
identified one.
Authentication: PRO required for full results. Free users receive the newest move only,
with up to 5 calls. Move-level counts remain intact. totalCount reports the servable moves
before the limit or preview is applied.
A known stock with no qualifying move returns 200 with an empty moves array andtotalCount: 0. An unknown symbol returns 404 entity_not_found with suggestions. An ETF
returns 404 ticker_is_etf with an analyst aggregate pointer.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol, including supported aliases |
limit |
int | No | 10 | Moves to return, newest first; capped at 50, below 1 returns 400 invalid_limit |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/called-it?limit=10"
Response Schema:
| Field | Type | Description |
|---|---|---|
isPreview |
boolean | true when the caller receives the FREE preview |
previewReason |
string | "PRO_REQUIRED" or null |
upgrade |
object | Present only in a preview; carries plan, message, price, url, and relay |
totalCount |
int | Servable moves before the limit or preview |
data.ticker |
string | Canonical ticker |
data.count |
int | Moves returned |
data.moves |
array | Moves ordered by moveEndDate descending |
Move:
| Field | Type | Description |
|---|---|---|
insightId |
string | Identifier of the insight describing this move |
generatedAt |
int | Insight generation time in epoch seconds |
moveStartDate |
string | First date of the move window, YYYY-MM-DD |
moveEndDate |
string | Last date of the move window, YYYY-MM-DD |
movePct |
decimal | Signed price change over the move window, in percent |
moveWindowSessions |
int | Trading sessions in the move window |
lookbackDays |
int | Pre-move lookback for price-target revisions |
coveringFirms |
int | Firms with a price target in the lookback |
revisedWithMove |
int | Firms that revised in the move's direction |
revisedAgainstMove |
int | Firms that revised against the move's direction |
leftUnchanged |
int | Firms whose target was left unchanged |
calls |
array | Same-direction revisions; capped at 5 in the FREE preview |
Call:
| Field | Type | Description |
|---|---|---|
firm |
string | Firm that published the target |
analystName |
string or null | Named analyst, or null when the note did not identify one |
attribution |
string | firm or analyst |
priceTarget |
decimal | Published revised price target |
priorPriceTarget |
decimal | Last price target before the lookback window, or the first target in the window when no earlier target exists |
publishedOn |
string | Publication date, YYYY-MM-DD |
daysBeforeMove |
int | Calendar days from publication to the start of the move |
A null analystName leaves the call attributed to the firm; it does not mean no call was made.
GET /{ticker}/actions
Returns recent analyst upgrade and downgrade actions for a ticker, newest first.
Authentication: PRO required. Free users receive the 3 most recent actions in full.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol |
lookbackDays |
int | No | 90 | Days of history to return |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/actions?lookbackDays=30"
Action object:
| Field | Type | Description |
|---|---|---|
ticker |
string | Stock ticker |
actionDate |
string | Date of the action (ISO date) |
firm |
string | Brokerage/research firm name |
actionType |
string | UPGRADE, DOWNGRADE, INITIATE, REITERATE, or OTHER |
fromGrade |
string | Previous rating (e.g. "Hold"), nullable on initiations |
toGrade |
string | New rating (e.g. "Buy"), nullable on coverage drops |
Grade vocabulary. fromGrade and toGrade are stored with one spelling per rating, so you can
group on the string directly. The ratings sort into six tiers, and two spellings in the same tier
are the same rating even when the words differ (Market Perform and Perform). Compare tiers, not
strings, when the question is whether a desk actually moved.
| Tier | Reads as | Spellings |
|---|---|---|
| 5 | Highest conviction | Strong Buy, Conviction Buy, Top Pick |
| 4 | Bullish | Buy, Overweight, Outperform, Positive, Accumulate, Speculative Buy, Long-Term Buy, Market Outperform, Sector Outperform, Outperformer |
| 3 | Neutral | Neutral, Hold, Equal-Weight, Market Perform, Sector Perform, In-Line, Peer Perform, Perform, Sector Weight, Hold Neutral, Mixed, Fair Value |
| 2 | Bearish | Underweight, Underperform, Reduce, Sector Underperform, Market Underperform, Negative, Cautious |
| 1 | Sell | Sell |
| 0 | Strongest conviction sell | Strong Sell |
A rating a research firm invents that is not on this list is passed through exactly as the firm
published it rather than being forced into a tier or discarded, so treat the table as the ratings we
recognise today, not as a closed enum your parser can rely on.
GET /{ticker}/estimates
Returns forward EPS estimates and recent earnings surprise history for a ticker.
Authentication: PRO required. Free users receive 1 estimate (current quarter) + 2 most recent surprises.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol |
Response shape:
| Field | Type | Description |
|---|---|---|
data.estimates |
array | Forward EPS estimate periods (PRO: full history; FREE: 1 row) |
data.surprises |
array | Earnings surprise history (PRO: full history; FREE: 2 rows) |
The estimates array elements describe a fiscal period with low/mean/high EPS estimates and the number of contributing analysts. The surprises array elements describe a past report with actual EPS, estimate, and surprise percent. Refer to the SDK type definitions for full field lists.
GET /activity
Returns market-wide recent analyst actions across all covered tickers, paged. Ordered by action date descending, with ties broken by ticker then id ascending, a total order so paging is stable.
Authentication: API key required; the first page is full data on every tier. Free keys receive the first 50 rows of the window as a complete response (isPreview: false); requesting limit above 50 or an offset past row 50 returns the free in-allowance slice with previewReason: "PRO_REQUIRED". PRO keys page the whole window.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lookbackDays |
int | No | 30 | Days of history to search |
limit |
int | No | 50 | Page size, capped at 500. Returns 400 invalid_limit below 1 |
offset |
int | No | 0 | Rows to skip. Returns 400 invalid_offset when negative |
actionTypes |
string | No | - | CSV filter on action type: any of UPGRADE, DOWNGRADE, INITIATE, REITERATE, OTHER (case-insensitive). Unknown values return 400 invalid_actionTypes |
Response: { isPreview, previewReason, totalCount, data: [...] }. totalCount is the number of actions in the whole lookbackDays window after the actionTypes filter, not the size of the page, so offset + data.length < totalCount tells you another page is available.
Roughly 83% of all analyst actions are REITERATE: an analyst confirming a rating they did not change. If you want actual rating changes (the usual question), filter for them:
# real rating changes only: upgrades, downgrades, and coverage initiations
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/activity?actionTypes=UPGRADE,DOWNGRADE,INITIATE"
Roughly 70 to 110 rating actions land on an active market day, and a heavy day runs a little over 100. Because rows come back newest first, the default 50-row page is typically filled by the newest day alone, and raising lookbackDays on its own returns no additional history. Raise limit for a wider slice, or walk the window with offset:
# every action in the last 30 days, 500 at a time
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/activity?lookbackDays=30&limit=500&offset=0"
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/activity?lookbackDays=30&limit=500&offset=500"
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/activity?lookbackDays=7"
Response shape matches the per-ticker /actions endpoint, with each entry's ticker field identifying the stock.
GET /people/{slug}
Returns one analyst: the firms they have published under, the window of notes we hold at each, and the tickers they cover.
slug is a stable public handle derived from the analyst's name, lowercased and hyphenated: dan-ives, stacy-rasgon, catherine-obrien. You do not have to guess one. Every named analyst returned by /{ticker}/coverage carries the slug that addresses their profile.
What this endpoint does not return: there is no accuracy score, hit rate, ranking or any other derived judgment about the individual. This is call history: who said what, when, and where it was reported. Scored track records are a separate piece of work with its own statistical and legal review, and nothing here should be read as a rating of the person.
Authentication: PRO required. Free keys receive the profile with the coverage book truncated to the 5 most recently covered tickers; totalCount reports how many there are in full.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slug |
path | Yes | - | Analyst slug, e.g. dan-ives |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/people/dan-ives"
Response shape:
| Field | Type | Description |
|---|---|---|
data.slug |
string | Stable public handle for this analyst |
data.name |
string | Display name |
data.role |
string | sell_side_equity |
data.mostRecentFirm |
string | The firm this analyst most recently published under |
data.firms |
array | { firm, firstSeen, lastSeen, mostRecent }, most recent first |
data.firstSeen |
string | ISO date of the earliest note we hold from this analyst |
data.lastSeen |
string | ISO date of the most recent note |
data.noteCount |
int | Price target notes attributed to this analyst |
data.tickerCount |
int | Distinct tickers covered |
data.coverage |
array | { ticker, noteCount, firstNote, lastNote, latestPriceTarget, latestFirm } (PRO: full; FREE: 5 rows) |
firstSeen and lastSeen are observation windows, not employment dates. They are the dates of the first and last note we hold from that analyst at that firm. An analyst who has not published recently keeps a lastSeen in the past, and mostRecentFirm says where they last published rather than where they work today. Do not render either as a hire or departure date.
GET /people/{slug}/calls
Returns one analyst's price target notes, newest first, paged.
Ordered by published date descending with the row id as the final tie-break, a total order, so walking the history with offset never drops or repeats a row. That matters more here than it looks: a single roundup article carries several of one analyst's notes at an identical timestamp.
Authentication: API key required. Free keys receive the first 25 rows of the history as a complete response (isPreview: false); asking for limit above 25 or an offset past row 25 returns the free in-allowance slice with previewReason: "PRO_REQUIRED". PRO keys page the whole history.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
slug |
path | Yes | - | Analyst slug, e.g. dan-ives |
limit |
int | No | 25 | Page size, capped at 200. Returns 400 invalid_limit below 1 |
offset |
int | No | 0 | Rows to skip. Returns 400 invalid_offset when negative |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/people/dan-ives/calls?limit=25&offset=0"
Call object:
| Field | Type | Description |
|---|---|---|
publishedDate |
string | ISO date the note was published |
ticker |
string | Stock the note is about |
firm |
string | Firm the analyst published under at the time |
priceTarget |
number | The target in the note, nullable |
adjPriceTarget |
number | Split-adjusted target, nullable |
priceWhenPosted |
number | Share price at publication, nullable |
newsTitle |
string | Headline of the report that carried the note |
newsUrl |
string | Link to that report |
newsPublisher |
string | Publisher that carried it |
Response: { isPreview, previewReason, totalCount, data: [...] }. totalCount is the analyst's whole attributed history, not the page size, so offset + data.length < totalCount tells you another page is available.
Dates are day granularity on purpose. Publisher timestamps are not comparable across sources: a note filed after the US close is dated the next day by some publishers while the matching rating action keeps the session date. A time of day would advertise precision the data does not have.
A slug that matches no analyst returns 404 not_found rather than an empty page, so you can tell "this analyst has published nothing we hold" apart from "this analyst does not exist".
GET /{ticker}/coverage
Returns who covers a stock and what they most recently said, grouped by firm, most recently active firm first.
This is the one-call answer to "who covers AMD and what do they say". Each row is a firm, the individual analysts we can name on that firm's desk, that firm's most recent price target note, and that firm's most recent rating action.
Authentication: PRO required. Free keys receive the 5 most recently active firms, with every response-level count intact and a top-level totalCount carrying the full number of covering firms; the untruncated PRO response omits totalCount.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ticker |
path | Yes | - | Stock ticker symbol (e.g. AMD) |
lookbackDays |
int | No | 365 | Coverage window in days, capped at 1825. Returns 400 invalid_lookbackDays below 1 |
Example Request:
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AMD/coverage?lookbackDays=180"
Response shape:
| Field | Type | Description |
|---|---|---|
data.ticker |
string | Canonical ticker |
data.windowDays |
int | Window actually applied after clamping |
data.asOf |
string | ISO date the response was built |
data.firmCount |
int | Firms that covered the ticker in the window: at least one price target note or at least one rating action |
data.ratingOnlyFirmCount |
int | How many of firmCount appear on a rating action alone, with no price target note in the window. Firms that published a target are firmCount - ratingOnlyFirmCount |
data.ratingBuckets |
object | { buy, hold, sell, unrated, total }: how the whole book's current ratings split. Counted over every covering firm before the FREE truncation, so total equals firmCount on both tiers |
data.namedAnalystCount |
int | Distinct named analysts across those firms |
data.noteCount |
int | Price target notes in the window |
data.attributedNoteCount |
int | Notes that name an individual analyst |
data.unattributedNoteCount |
int | Notes that name no individual |
data.attributionNote |
string | Plain-language statement of what an absent name means |
data.coverage |
array | Firm rows (PRO: all; FREE: 5) |
Firm row:
| Field | Type | Description |
|---|---|---|
firm |
string | Research firm |
analysts |
array | { slug, name, noteCount, firstNote, lastNote, latestPriceTarget }, possibly empty |
noteCount |
int | This firm's notes in the window |
attributedNoteCount |
int | Of those, how many name an analyst |
unattributedNoteCount |
int | Of those, how many do not |
firstNote / lastNote |
string | ISO dates bounding this firm's activity in the window |
latestNote |
object | The firm's most recent note: { publishedDate, analyst, priceTarget, adjPriceTarget, priceWhenPosted, newsTitle, newsUrl, newsPublisher }. analyst is an object { slug, name }, or null when the publisher named nobody |
firmRating |
object | The firm's most recent rating action: { rating, priorRating, actionType, date }, nullable |
Not every note names its analyst, and the ones that do not are still here. A large share of price target notes arrive with no individual named, and whether a note is named is a property of the publisher that carried it rather than of the note itself. The share is high and varies enormously by ticker: across a 16-ticker large-cap sample on a 365-day window, 52% of notes named nobody, ranging from 11% on CRM to 64% on BA. Do not hardcode a rate; read attributedNoteCount and unattributedNoteCount on the response you actually received. Dropping unnamed notes would therefore report a firm as absent from a stock it demonstrably published on, and would do it unevenly by publisher. So they are kept and counted: a firm can appear with an empty analysts array and a non-zero noteCount, and latestNote.analyst is null when the report named nobody. An absent name means the report did not identify one, never that the note did not happen.
A firm can cover a stock without publishing a price target. The upstream price target feed goes quiet on a desk while that desk's rating actions keep arriving, so coverage means "published a price target or a rating action in the window". A firm in that state comes back as an ordinary row with noteCount: 0, an empty analysts array, null for firstNote / lastNote / latestNote, and its firmRating set. Rows are ordered by whichever came later, the firm's last note or its last rating action, so a rating-only firm interleaves by its rating date. Read noteCount on the row rather than assuming latestNote is present, and read ratingOnlyFirmCount before describing firmCount as firms with price targets.
ratingBuckets counts the coverage book, not the vendor's consensus. The four counts come from the firmRating of every firm in data.coverage, including the rating-only firms, and they are computed before the FREE preview cuts the list to 5 rows. A free key therefore gets 5 firm rows and the true split across all of them, which is what makes it safe to render a percentage next to a truncated list. buy is a rating of Strong Buy through Outperform, hold is Hold, Neutral, Equal-Weight and the Perform family, sell is Underperform through Strong Sell. unrated holds firms with no rating action in the window and firms whose grade spelling we do not recognise, because both mean the same thing: we cannot say what that desk calls the stock today. A grade is never guessed into a direction from its wording. buy + hold + sell + unrated == total == firmCount, so pick your own denominator knowingly. This is a different population from the strongBuy / buy / hold / sell / strongSell counts on /{ticker}/consensus, which come from the upstream consensus feed and count a different set of desks. Do not put the two side by side in one view, and do not expect them to agree.
firmRating belongs to the firm, not to a person. Rating actions are published at firm level: the upstream feed carries the firm, the grade and the date, with no individual attached. Do not render a firm rating as a named analyst's rating.
analysts[].slug addresses /people/{slug}, so a coverage response is the natural entry point into a profile. A named analyst we hold no profile for comes back with a name and a null slug rather than being dropped or linked to a guess.
For an ETF, which has no analyst desk, this returns a pointer whose first entry is the
holdings-weighted analyst consensus endpoint at /api/v1/etfs/{ticker}/aggregates/analyst.
Errors
| Status | Code | Description |
|---|---|---|
| 400 | invalid_lookbackDays | /{ticker}/coverage only: lookbackDays below 1. Values above 1825 are capped, not rejected, and /{ticker}/actions accepts any lookbackDays |
| 400 | invalid_parameter | A query parameter that cannot be parsed as its type, for example limit=abc |
| 400 | invalid_limit | limit below 1 on /activity (values above 500 are capped, not rejected) |
| 400 | invalid_offset | offset below 0 on /activity |
| 400 | invalid_actionTypes | actionTypes on /activity contains a value outside UPGRADE, DOWNGRADE, INITIATE, REITERATE, OTHER |
| 400 | invalid_lookbackDays | lookbackDays below 1 on /{ticker}/coverage (values above 1825 are capped, not rejected) |
| 400 | invalid_from | from on /{ticker}/consensus/history is not formatted YYYY-MM-DD |
| 400 | invalid_to | to on /{ticker}/consensus/history is not formatted YYYY-MM-DD |
| 400 | invalid_range | from is after to on /{ticker}/consensus/history |
| 400 | invalid_limit | limit is below 1 on /{ticker}/consensus/history; values above 366 are capped |
| 400 | invalid_limit | limit is below 1 on /{ticker}/called-it; values above 50 are capped |
| 401 | api_key_required | No API key on a programmatic call |
| 429 | quota_exceeded | Monthly request quota for your tier is used up |
| 404 | entity_not_found | /{ticker}/consensus, /{ticker}/consensus/history, and /{ticker}/called-it: no stock is tracked under that symbol. Carries up to three suggestions; resolve with GET /api/v1/kb/entities/search?q= and retry |
| 404 | no_coverage | /{ticker}/consensus: no current analyst record exists; /{ticker}/consensus/history: no history exists at all. No suggestions, and retrying a different spelling cannot help |
| 404 | ticker_is_etf | /{ticker}/consensus, /{ticker}/consensus/history, and /{ticker}/called-it: the symbol is a fund. seeInstead starts with /api/v1/etfs/{ticker}/aggregates/analyst |
| 404 | not_found | Other analyst paths: the slug on /people/{slug} matches no analyst |
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/analyst/{ticker}/consensus
Price targets + recommendation distribution
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/consensus"GET/api/v1/analyst/{ticker}/consensus/history
Daily analyst consensus observations
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/consensus/history"GET/api/v1/analyst/{ticker}/called-it
Price-target revisions preceding large moves
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/called-it"GET/api/v1/analyst/{ticker}/actions
Recent analyst upgrades and downgrades
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/actions"GET/api/v1/analyst/{ticker}/estimates
Forward EPS estimates and earnings surprise history
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/estimates"GET/api/v1/analyst/activity
Market-wide recent analyst actions
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/activity"GET/api/v1/analyst/people/{slug}
One analyst: firms published under, and coverage book
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/people/{slug}"GET/api/v1/analyst/people/{slug}/calls
One analyst's price target notes, newest first, paged
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/people/{slug}/calls"GET/api/v1/analyst/{ticker}/coverage
Who covers this stock, and what they most recently said
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
"https://app.sentisense.ai/api/v1/analyst/AAPL/coverage"