SentiSense Rating API

The daily 0 to 100 score and A to F letter grade for a US stock: the rank of a seven-dimension blend of crowd sentiment, smart money, options positioning, analysts, fundamentals, earnings and technicals, less its graded risk adjustments, with the full composition behind it.

Free (API key required; requests consume monthly quota)1 endpoints

Overview

The SentiSense Rating is a letter grade, A to F, that says where one stock stands today against every other stock we rate. It blends seven independent signal families: crowd sentiment, smart money, options positioning, analysts, fundamentals, earnings and technicals. The same grade drives the Rating card in the SentiSense app.

The score is a sum of two documented constituents.

score = percentile - sum(riskAdjustments[].points)
        each adjustment worth up to 12 points, most of them graded
        floored at 10 when fewer than 6 of the 7 dimensions are available, and at 0 otherwise

percentile is the rank: 0 to 100, how the stock's blended signals compare with the rest of the rated universe that day. riskAdjustments pairs each condition with what it actually cost, and penaltyPoints is their sum, to one decimal. score is the result, 0 to 100 with one decimal, and it is the headline: letter is a band of score, so the two can never disagree. All of them are returned side by side so you can check the arithmetic yourself.

Most adjustments are graded, so do not assume a flat charge. Seven of the eleven conditions scale with how far past the threshold the stock sits, up to 12 points; four are flat 12s because they are genuinely binary. Read the cost from riskAdjustments[].points, never from 12 x riskConditions.length.

Report the score and the letter; the percentile is the rank before the adjustments. A response reading "percentile": 100.0, "penaltyPoints": 14.4, "score": 85.6, "letter": "B" is correct and internally consistent. Describe it as "B, 85.6 out of 100, ranked in the 100th percentile with 14.4 points of risk adjustments", not as a data error and not by quoting the rank as though it were the grade. bucketLetter is the band the rank alone would land in, available for callers who want it.

What it is not. The Rating is not a recommendation, a price target, a forecast of returns, or a statement that a stock suits anyone. It describes where a stock sits today across public signals. When you render it or summarize it, describe it as a score and a rank ("B, 85.6 out of 100, ranked in the 80th percentile of rated stocks today"). Do not translate a letter into buy, sell, hold, long, short or avoid: those are trade instructions and the grade does not carry that meaning.

The Rating is not the SentiSense Score. The Score is the continuous crowd-sentiment number for a stock, and it is one of the seven inputs here. The Rating is the composite grade over all seven. Those are the only two headline measures we publish, and they are different objects with different scales.

The letter bands, on the score:

Letter Score
A 90 and above
B 70 to 89.9
C 30 to 69.9
D 10 to 29.9
F Below 10

The bands are not fixed shares of the market: a letter is whatever band the score lands in. At most one stock in ten can carry an A, since only the top tenth of ranks reaches 90 before any adjustment, and in practice far fewer do.

The eleven risk conditions. Each is worth up to 12 points, several can apply at once, and each is a value in the riskConditions array:

riskConditions value When it applies Points
thin_coverage Thin coverage: currently 6 points for each missing dimension once fewer than 6 of the 7 are available, so 5 dimensions cost 6 and 4 cost 12, while 6 or 7 cost nothing. Below 6 the score is also floored at 10 up to 12, graded
insider_selling The clustered_insider_selling flag is active: currently 5 or more distinct insiders filing open-market sales in the trailing 90 days, none under a scheduled 10b5-1 plan, and no insider purchasing in the same window 12
institutional_outflow The institutional_outflow_against_price flag is active, scaled by how deep in the bottom quarter of net institutional change the stock sits up to 12, graded
unprofitable Trailing twelve month operating margin or net margin is not positive 12
no_fundamentals No filed financials, so the profitability and leverage conditions cannot be evaluated 12
high_leverage Leverage above the current threshold: 6 points per unit of debt to equity above 2 up to 12, graded
unseasoned_listing Unseasoned listing: currently fewer than 250 trading sessions in the past 400 days. Counted in sessions rather than from a listing date, because there is no reliable listing date behind the grade 12
weak_dimension The weakest available dimension sits below the 20th percentile, currently 0.6 points per percentile below 20. up to 12, graded
small_market_cap Small company: market capitalization currently under 2 billion dollars, scaled by the shortfall up to 12, graded
thin_liquidity Thinly traded: dollar volume in the latest session currently under 10 million dollars, scaled by the shortfall up to 12, graded
extended_price Extended price: currently within 5 percent of the 52-week high and more than 25 percent above the 200-day moving average, scaled by the excess above 25 percent up to 12, graded

unusual_options_flow is informational and costs nothing.

Condition thresholds, and the rate at which a graded one accrues, are tuned as calibration data accumulates and may change without a version bump; the constituents, their maximum and the formula do not change without one. The eleven names above are a closed vocabulary: treat an unrecognized value as a condition this client does not know about, costing whatever riskAdjustments says it cost, rather than as an error.

The seven dimensions and their weights:

Dimension key Label Weight What it reads
crowd Crowd sentiment 16% The 7-day SentiSense Score (70%) and 7-day social dominance (30%)
smart_money Smart money 16% Institutional 13F net change, insider flow balance, congressional flow balance
options Options positioning 12% The options-implied sentiment reading
analysts Analysts 12% Share of favorable analyst ratings
fundamentals Fundamentals 20% Operating margin, return on equity, revenue growth
earnings Earnings 8% Latest EPS surprise
technicals Technicals 16% Where the price sits versus its own history: distance from its 200-day and 50-day averages, its twelve-month path, and how calm or violent its recent sessions have been

Each dimension is turned into a percentile among the rated stocks that have it that day, then blended with those fixed weights. The weights are engineering priors ordered by freshness and by how much independent information each family carries. They are not the output of a return backtest, and a later version may revise them. The full derivation, including every availability floor and the sub-signal weights inside smart money, is published at sentisense.ai/methodology.

A missing dimension counts as the median. An unavailable dimension is scored at the 50th percentile at its full weight rather than dropped, so thin coverage pulls a grade toward the middle instead of letting two strong readings carry a stock to the top. A stock is rated only when at least 4 of the 7 dimensions are available and those dimensions carry at least 50 percent of the total weight. Otherwise the response is an ordinary 200 with rated: false. A stock is never given a C by default, so treat "not rated" as an absence of evidence, not a neutral grade. This is why smaller companies with thin coverage, no options market and no analyst following are often unrated.

Freshness: the Rating is recomputed once per trading day, at 07:15 Eastern Time on the morning after each trading session, which puts the run on Tuesday through Saturday. It reads the prior session's crowd sentiment, the options reading computed that morning from the prior session's chain, the prior session's closing prices, and the latest available filings, consensus and earnings data. asOf on the response is the date of the run. Polling faster than daily returns the same values, so cache on asOf.

Access: an API key is required on every call, and each request counts against your monthly quota (Free: 1,000 requests/month; PRO: no monthly cap) as well as your per-minute rate limit. There is no PRO gate on the data itself: every field below is served in full on the Free tier. Anonymous calls return 401 api_key_required.


GET /{ticker}

Today's Rating for one stock: the score, the letter that bands it, the rank and adjustments it was built from, the composite, and the per-dimension composition behind it.

Authentication: API key required

Parameters:

Parameter Type Required Description
ticker path Yes Stock ticker (e.g. AAPL). Case-insensitive; dual-class symbols resolve to their canonical form

Example Request:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v1/rating/AAPL"

Response (rated): a flat object, no {isPreview, data} wrapper.

Field Type Description
ticker string The resolved ticker
kbEntityId string The ontology entity this ticker resolved to, usable on the Metrics and Entities APIs
rated boolean true on this shape
score number The headline. 0 to 100 with one decimal: percentile minus penaltyPoints, floored at 10 on thin coverage and at 0 otherwise
letter string A, B, C, D or F. The band score falls in, and the one to display beside it
percentile number 0 to 100, the stock's rank among the stocks rated today. This is what score was built from, before the risk adjustments
penaltyPoints number What the risk adjustments took off, to one decimal: the sum of riskAdjustments[].points, so percentile - penaltyPoints reproduces score up to the floor
riskAdjustments array of object { condition, points } per adjustment, points to one decimal. This is where the cost of each condition lives, and it is not a constant: most adjustments are graded
riskConditions array of string The names in riskAdjustments that cost more than zero, from the closed vocabulary above. Empty when the stock carries none, in which case score equals percentile
bucketLetter string The band percentile alone would fall in. Equal to letter when the stock carries no risk conditions
composite number The weighted blend before it was ranked. Comparable within a day only; read percentile for anything cross-sectional
ratedCount integer How many stocks were rated in this run, the denominator behind percentile
asOf ISO date The run date
methodologyVersion string The methodology in force, e.g. 2026.09-v3. Changes to weights, floors or letter edges get a new version
dimensions array Always all seven rows, in a fixed order, each with a present flag
flags array Anomaly flags that were evaluable for this stock, each with an active boolean
disclaimer string The disclaimer to render alongside the grade

Each element of dimensions:

Field Type Description
key string crowd, smart_money, options, analysts, fundamentals, earnings, technicals
label string Display label for the dimension
percentile number or null The dimension's percentile among rated stocks that have it. null when present is false
raw number or null The underlying reading on its own scale. null on smart_money, which has no single raw number, and null when present is false
rawLabel string or null What raw measures, e.g. Operating margin, percent
present boolean Whether this dimension contributed. Branch on this, not on raw != null
subLegs array smart_money only: { key, label, raw, unit } for inst_13f, insider and congress. A leg with no activity in its window carries raw: null and was left out of the blend

Each element of flags is { key, label, active }, over clustered_insider_selling, institutional_outflow_against_price and unusual_options_flow. The first two are also risk conditions and take points off the score while active, clustered_insider_selling a flat 12 and institutional_outflow_against_price a graded amount; unusual_options_flow is informational. The array holds only the flags that could be evaluated for this stock, so a flag missing from the array means "not assessable here", which is not the same as active: false. Look flags up by key rather than by position.

Checking the arithmetic. In the example below the stock ranked at the 100th percentile and carries two risk conditions with different costs, a graded 2.4 and a flat 12.0, summing to the 14.4 in penaltyPoints; the score is 85.6, which bands to B. bucketLetter reports the A the rank alone would have given. Render the numbers together ("B, 85.6 out of 100, ranked in the 100th percentile with 14.4 points of risk adjustments") rather than quoting any one of them as the grade.

{
  "ticker": "AAPL",
  "kbEntityId": "kb/company/1",
  "rated": true,
  "score": 85.6,
  "letter": "B",
  "percentile": 100.0,
  "penaltyPoints": 14.4,
  "riskAdjustments": [
    { "condition": "weak_dimension", "points": 2.4 },
    { "condition": "high_leverage", "points": 12.0 }
  ],
  "riskConditions": ["weak_dimension", "high_leverage"],
  "bucketLetter": "A",
  "composite": 0.19,
  "ratedCount": 1000,
  "asOf": "2026-09-03",
  "methodologyVersion": "2026.09-v3",
  "dimensions": [
    {
      "key": "crowd",
      "label": "Crowd sentiment",
      "percentile": 84.0,
      "raw": 9.2,
      "rawLabel": "7-day SentiSense Score",
      "present": true
    },
    {
      "key": "smart_money",
      "label": "Smart money",
      "percentile": 45.0,
      "raw": null,
      "rawLabel": null,
      "present": true,
      "subLegs": [
        { "key": "inst_13f", "label": "13F net change", "raw": 4.0, "unit": "%" },
        { "key": "insider", "label": "Insider flow balance", "raw": -1.0, "unit": "ratio" },
        { "key": "congress", "label": "Congress flow balance", "raw": -0.4, "unit": "ratio" }
      ]
    },
    {
      "key": "options",
      "label": "Options positioning",
      "percentile": 68.0,
      "raw": 0.18,
      "rawLabel": "Options sentiment",
      "present": true
    },
    {
      "key": "analysts",
      "label": "Analysts",
      "percentile": 30.0,
      "raw": 57.0,
      "rawLabel": "Share of favorable analyst ratings",
      "present": true
    },
    {
      "key": "fundamentals",
      "label": "Fundamentals",
      "percentile": 95.0,
      "raw": 33.2,
      "rawLabel": "Operating margin, percent",
      "present": true
    },
    {
      "key": "earnings",
      "label": "Earnings",
      "percentile": 38.0,
      "raw": 0.07,
      "rawLabel": "Latest EPS surprise, percent",
      "present": true
    },
    {
      "key": "technicals",
      "label": "Technicals",
      "percentile": 61.0,
      "raw": 7.9,
      "rawLabel": "Distance from the 200-day average, percent",
      "present": true
    }
  ],
  "flags": [
    { "key": "clustered_insider_selling", "label": "Clustered insider selling", "active": false },
    { "key": "institutional_outflow_against_price", "label": "Institutional outflows against price", "active": false },
    { "key": "unusual_options_flow", "label": "Unusual options flow", "active": true }
  ],
  "disclaimer": "The SentiSense Rating is a relative, automatically generated research signal for informational and educational purposes only. ..."
}

When a stock is not rated

A covered ticker that misses the coverage floor returns 200 with rated: false. This is a normal state, not an error, and it is common: index and sector ETFs are never rated, and thinly covered small caps often are not.

Field Type Description
ticker / kbEntityId / asOf Same as the rated shape
rated boolean false
reason string not_rated_today, insufficient_dimensions or insufficient_coverage_weight
presentDimensions array The dimension keys that did have data, e.g. ["crowd", "analysts"]. Empty when none did
dimensionsPresent integer How many of the seven had data. A separate field from presentDimensions, and the one that is optional: it is omitted when the run holds no record for this ticker at all. Read the length of presentDimensions if you need a count you can always rely on
dimensions array Still all seven rows, with present false on the missing ones, so a composition UI can render the gaps
flags array Empty
disclaimer string Same text as the rated shape

score, letter, percentile, penaltyPoints, riskAdjustments, riskConditions, bucketLetter, composite, ratedCount and methodologyVersion are absent entirely on this shape. Branch on rated before reading any of them.

presentDimensions and dimensionsPresent are two different fields whose names are one transposition apart: the first is the list of keys, the second the integer count. A stock the run looked at and refused carries both, for example "reason": "insufficient_dimensions" with "dimensionsPresent": 2 and "presentDimensions": ["crowd", "analysts"]. A stock the run never reached carries not_rated_today with an empty list and no count at all, which is the shape below.

{
  "ticker": "SPY",
  "kbEntityId": "kb/etf/3",
  "rated": false,
  "asOf": "2026-09-03",
  "reason": "not_rated_today",
  "presentDimensions": [],
  "dimensions": [
    {
      "key": "crowd",
      "label": "Crowd sentiment",
      "percentile": null,
      "raw": null,
      "rawLabel": "7-day SentiSense Score",
      "present": false
    }
  ],
  "flags": [],
  "disclaimer": "The SentiSense Rating is a relative, automatically generated research signal for informational and educational purposes only. ..."
}

An identifier that resolves to no known stock returns 404 entity_not_found with a hint pointing at GET /api/v1/kb/entities/search?q= on the Entities API. That is a different answer from rated: false: one means "we do not know this ticker", the other means "we know it and did not grade it today".


From the SDKs

Both SDKs wrap the endpoint and return the same fields. The letter is the band of score; percentile is the rank before the risk adjustments.

Python (sentisense 0.49.0 or later):

from sentisense import SentiSenseClient

client = SentiSenseClient(api_key="YOUR_KEY")
rating = client.get_rating("AAPL")
if rating.rated:
    print(rating.letter, rating.score, rating.percentile)
    for adj in rating.riskAdjustments:
        print(adj.condition, adj.points)

Node (npm package sentisense 0.51.0 or later):

import SentiSense from "sentisense";

const client = new SentiSense({ apiKey: process.env.SENTISENSE_API_KEY });
const r = await client.stocks.getRating("AAPL");
if (r.rated) {
  console.log(r.letter, r.score, r.percentile);
  for (const adj of r.riskAdjustments ?? []) console.log(adj.condition, adj.points);
}

Rating history

Each daily run is stored as a point on the sentisense_rating metric, so the Metrics API serves the history with the same call shape as every other metric:

curl -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  "https://app.sentisense.ai/api/v2/metrics/entity/AAPL/metric/sentisense_rating?startTime=1785900000000"

The flat value on each point is the score, 0 to 100 with one decimal (the number the letter is derived from), not the letter, not the percentile and not the composite. The letter, the composite and every per-dimension figure ride along under metricValue.properties: letter, composite, ratedCount, coverageWeight, dimensionsPresent, methodologyVersion, a dim<Name>Pct and dim<Name>Raw pair per dimension, the three sm* smart-money legs, and a flag<Name> boolean per flag.

Points appear only for days the stock was rated, so a gap in the series means "not rated that day" rather than a missing reading. The series starts at the first published run and deepens by one point per trading day, so treat it as short until it has accumulated. Because the grade is a rank, its history stays comparable even when the weights are revised.


Screening on the Rating

The Screener API exposes the letter as a filterable field, so you can put the grade in the same WHERE clause as the SentiSense Score, analyst consensus and technicals.

SENTISENSE_RATING is a string field over A, B, C, D, F and accepts IN and NOT_IN only. It is sortable, and sorting resolves to the underlying score rather than the letter's alphabetical order, so the ordering inside a letter is defined and DESC puts the highest score first. Unrated stocks match no letter and are excluded in both directions, the same null rule as every other field.

curl -X POST https://app.sentisense.ai/api/v1/screener/execute \
  -H "X-SentiSense-API-Key: ss_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plan": {
      "filters": [
        { "fieldName": "SENTISENSE_RATING", "op": "IN", "values": ["A", "B"] },
        { "fieldName": "ANALYST_BUY_RATIO_PCT", "op": "LTE", "value": 40 }
      ],
      "sort": { "fieldName": "SENTISENSE_RATING", "dir": "DESC" }
    },
    "limit": 25
  }'

Matching rows carry ratingLetter, ratingScore and ratingPercentile alongside the other screener columns, so one screen returns the grade, the score behind it and the rank it started from without a second call. The /stocks table sorts its Rating column on ratingScore for the same reason.


Errors

Status Code Description
200 - Success. rated: false when the stock missed the coverage floor for this run; this is a normal state, not an error
401 api_key_required No API key on the call
404 entity_not_found The identifier matches no known stock. The message points at GET /api/v1/kb/entities/search?q=
429 rate_limit_exceeded Per-minute rate limit exceeded (Free: 30/min, PRO: 300/min)
429 quota_exceeded Monthly request quota exhausted (Free: 1,000/month; PRO has no monthly cap)

Educational use only

The SentiSense Rating is a relative, automatically generated research signal provided for informational and educational purposes. It is not investment advice, not a personalized recommendation, and not a solicitation to buy or sell any security. Data may be delayed, incomplete or inaccurate, and the weights behind the grade have not been validated against forward returns. Verify with official sources and consult a qualified financial professional before making any investment decision. Use of the API is subject to the API Terms of Service.

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/rating/{ticker}

Today's score, letter and seven-dimension composition for a stock

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