/v2/snapshot for the headline multiples, then /v2/valuation for the own-history context that tells you whether those multiples are high or low for this stock. Both routes are on the Free plan, so the full check costs two calls and no upgrade. Don’t reach for /v2/fundamentals here; that returns the full per-quarter arrays, and most valuation prompts only need the lean direction layer /v2/valuation already carries.
The one thing this guide exists to teach: a low percentile is not a cheapness verdict. It is a rank within the symbol’s own past range. Whether a low rank is interesting depends entirely on why the multiple fell, which the response hands you directly.
The {value, vs_own_history, change} pattern
On /v2/snapshot, pe_ttm, ps_ttm, and friends each ship as a {value, vs_5y} object in the fundamentals_quick block — a value with a coarse vs_5y band. A raw pe_ttm of 31 is not yet an answer: 31 is low for some names and rich for others. /v2/valuation resolves that by wrapping every multiple as {value, vs_own_history}, and adding a change decomposition on pe_ttm, ps_ttm, and the anchor multiple:
valueis the current multiple: 31.23x. This is the absolute level. NVDA at 31x earnings is not cheap in absolute terms — plenty of profitable megacaps trade in the teens.vs_own_historyranks that level inside the symbol’s own past.percentileis a map keyed by lookback window —{ "3y": 1, "5y": 0, "10y": 8 }says today’s P/E sits at the 1st percentile of the last three years, the 0th of the last five, and the 8th of the last ten. A window appears only when there is enough clean history for it; a young symbol may show one window or none rather than three identical, false ranks.median_5yis the magnitude anchor (57.66x — the multiple has usually been far higher).labelbands the 5-year percentile into one closed reading;nis the clean sample size behind it (1238 daily points here).changeis the decomposition that makes the rank legible: how much the price leg moved versus the fundamental leg over 1 year and 3 years, and adrivernaming which leg moved the multiple.
The label vocabulary — and what near_5y_low does NOT mean
label is one of five strings, scoped to the window in the name so you can never quote it as an absolute claim:
| label | 5-year percentile | reading |
|---|---|---|
near_5y_low | ≤ 20 | near the low end of its OWN 5-year range |
below_5y_median | 20–45 | below its own 5-year median |
near_5y_median | 45–55 | around its own 5-year median |
above_5y_median | 55–80 | above its own 5-year median |
near_5y_high | ≥ 80 | near the high end of its own 5-year range |
_3y_ infix (near_3y_low, and so on) so the scope is always explicit.
near_5y_low means low versus this symbol’s own five-year range. It does not mean cheap, undervalued, a buy, or low versus peers, sector, or any notion of fair value. A stock can sit at near_5y_low and still be one of the most expensive names in its industry — exactly the NVDA case above, where a near_5y_low P/E is still 31x. Report the label as what it is: a position within the symbol’s own history.
window_divergence: the rank depends on the lookback you chose
window_divergence: true fires when the included percentiles disagree by at least 35 points. It rides on the vs_own_history of whichever multiple diverges, not always pe_ttm. For live NVDA it appears on ps_ttm, whose percentile is { "3y": 4, "5y": 19, "10y": 47 } — the answer to “is this multiple low?” depends heavily on whether you look back three years or ten. When it is true, do not report a single percentile as the rank; say the rank is lookback-dependent and cite the windows. The flag is omitted entirely (not set to false) when the windows agree, or when fewer than two windows qualified, because one window cannot certify agreement — that is why NVDA’s pe_ttm (5-year 0, 10-year 8, an 8-point spread) carries no flag.
Decomposing a low percentile: did price drop, or did earnings grow?
A low percentile has two completely different causes, and they call for opposite write-ups:- The price leg fell — the stock sold off while fundamentals held. The multiple compressed because the numerator shrank.
- The fundamental leg grew faster than price — earnings or sales ran ahead of the stock. The multiple compressed because the denominator outran the numerator.
change block tells you which, per horizon. price_pct is the move in the price leg (market cap for P/x multiples, enterprise value for EV/x); fundamental_pct is the move in the matching fundamental (TTM EPS for P/E, revenue for P/S); driver classifies the pair. Note that the legs combine multiplicatively, not additively — (1 + price_pct/100) / (1 + fundamental_pct/100) reconstructs the change in the multiple — so never add the two percentages.
The driver is one of:
| driver | meaning |
|---|---|
fundamentals_outran_price | the fundamental grew faster than price; the multiple shrank because earnings/sales ran ahead |
price_outran_fundamentals | price grew faster than the fundamental; the multiple expanded |
price_fell_faster_than_fundamentals | both fell, price more; the multiple shrank on a selloff |
fundamentals_fell_faster_than_price | both fell, the fundamental more; the multiple expanded as earnings collapsed |
moved_together | neither leg meaningfully outran the other |
fundamentals_outran_price: price rose 45.91% while TTM EPS rose 109.97%. The P/E is at a 5-year low not because the stock dropped — it rose sharply — but because earnings more than doubled it. That is a very different story from a name whose multiple is low because the price collapsed, and your answer must say which one this is.
When a horizon can’t be decomposed, you get an envelope instead of numbers: { "available": false, "reason": "fundamental_sign_change" } when EPS crossed zero, "negative_earnings_both_periods" when it was negative at both ends, or "insufficient_history_for_1y_attribution" when the daily history doesn’t reach back a full year. A multiple with a low percentile but no usable change block is weaker evidence — say so.
history_regime: is the percentile comparing today’s company to one that no longer exists?
A top-level history_regime block guards the percentile axis itself:
regime_break: true means revenue or TTM EPS swung by at least 3x inside the lookback — the business changed scale enough that ranking today’s multiple against its five-year history is partly ranking it against a company that no longer exists. change_x is the swing as a multiple (revenue 11.6x, EPS 38.4x here), direction whether it was growth or contraction. When this fires, the percentile is less trustworthy as a “cheap or rich” gauge: the denominator of the old multiples belonged to a much smaller company. regime_break: false is a real assertion (the scale was stable); a { "available": false, "reason": "insufficient_statement_history" } envelope means there weren’t eight quarters to judge.
Read change and history_regime together: a low percentile, driven by fundamentals_outran_price, in a name with regime_break: true, is the signature of a fast-growing company whose multiple compressed as it scaled — not a discount waiting to be collected.
Worked example: is NVDA expensive?
One snapshot call, one valuation call. The valuation response for NVDA (anchor block plus the P/E multiple, regime block, and the lean trajectory shown; the live response also returnsps_ttm, pb, the EV multiples, a yields block, and returns_on_capital):
GET /v2/valuation?symbol=NVDA (trimmed)
stockcontext_valuation({ "symbol": "NVDA" }) and returns the identical envelope. valuation_context is the headline: it names which multiple to anchor on (anchor), why (rationale), and repeats that multiple’s value and label — but deliberately not a standalone percentile, so the headline can’t be quoted as a verdict. Use the per-multiple vs_own_history for the supporting detail.
The correct read assembles the parts rather than quoting the label:
NVDA’s P/E is near the low end of its own five-year range — but that is a statement about its history, not a cheapness call. The anchor P/E is 31.23x (Note what that answer does not say: it never calls NVDA cheap, undervalued, or a buy. “Low versus its own five-year P/E, because earnings outran price” is a description of returned data. A recommendation is not — and “cheap” is a claim the data does not support.near_5y_low), against a 5-year median of 57.66x. That low rank is not a selloff: over the past year price rose 45.91% while trailing EPS rose 109.97%, so thedriverisfundamentals_outran_price— the multiple compressed because earnings outran the stock, not because the stock fell.history_regime.regime_breakistrue(revenue swung ~11.6x and EPS ~38.4x over the window), so the five-year percentile partly ranks today’s NVDA against a much smaller company. Onpe_ttmthe windows agree (5-year 0, 10-year 8) so nowindow_divergencefires; it does fire onps_ttm, where the 3-year (4) and 10-year (47) percentiles tell different stories. Growth is still strong (revenue +70.68% YoYstable, EPS +109.97%accelerating). At 31x earnings in absolute terms, this is a high-quality megacap whose multiple is low for itself while still rich versus the broad market — not a discount. Freshness: end_of_day, as_of 2026-06-08T13:07:46Z, market closed.
Non-payers: the dividend field is absent, not zero
A multiple or yield that doesn’t apply comes back as an unavailable envelope, never a fabricated 0. TSLA pays no dividend, so itsyields block reports:
GET /v2/valuation?symbol=TSLA (yields, illustrative)
available: false as “StockContext has no dividend yield because the company doesn’t pay one,” and write that. Reporting a 0% yield would imply a paying stock that yields nothing, which is a different and wrong claim. Where NVDA’s anchor reads near_5y_low, a name whose multiples all read near_5y_high is sitting near the top of its own range — but apply the same discipline: that is a position within its own history, decomposed the same way (did price run, or did fundamentals shrink?), not a “premium” or “sell” verdict.
ETFs: valuation is unsupported
ETFs have no earnings, so equity multiples don’t exist for them./v2/valuation?symbol=SPY returns HTTP 200 with freshness: "unsupported", asset_type: "etf", and reason: "etf_no_valuation_multiples", not an error to retry. Say multiples aren’t available for that asset type and move on; the freshness reference covers the unsupported state and how to branch on it.
Going deeper: what is the price priced for?
The percentile tells you where a multiple sits in its own range. It does not tell you what growth the current price requires. When you need that — the actionable counterpart to a percentile — call/v2/priced-in: it solves the FCF growth rate today’s price already embeds and compares it to the company’s own realized growth. See the reverse-DCF guide.
Compare basket
Screen 2–12 names first, then run this check on the finalists only.