Analyst Ratings API

Wall Street analyst price targets, buy/hold/sell distribution, upgrade and downgrade actions, and forward EPS estimates with earnings surprise history.

PRO (free preview available)9 endpoints

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's
updatedAt 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 by
snapshotDate 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 and
totalCount: 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

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

GET/api/v1/analyst/{ticker}/consensus/history

Daily analyst consensus observations

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

GET/api/v1/analyst/{ticker}/called-it

Price-target revisions preceding large moves

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

GET/api/v1/analyst/{ticker}/actions

Recent analyst upgrades and downgrades

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

GET/api/v1/analyst/{ticker}/estimates

Forward EPS estimates and earnings surprise history

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

GET/api/v1/analyst/activity

Market-wide recent analyst actions

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

GET/api/v1/analyst/people/{slug}

One analyst: firms published under, and coverage book

Try It
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/analyst/people/{slug}"
Enter your API key to send requests

GET/api/v1/analyst/people/{slug}/calls

One analyst's price target notes, newest first, paged

Try It
curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \ "https://app.sentisense.ai/api/v1/analyst/people/{slug}/calls"
Enter your API key to send requests

GET/api/v1/analyst/{ticker}/coverage

Who covers this stock, and what they most recently said

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