# StockContext agent context
Decision-grade US stock and ETF market context for AI agents and the apps that run them, over REST and a hosted MCP server. One call returns context an agent can reason on: live quotes and performance, valuation read against a symbol's OWN history (percentiles and a window-scoped label like near_5y_low ... near_5y_high — a rank within the symbol's own range, never a cheap/undervalued verdict, with a price-vs-fundamentals decomposition), a reverse-DCF that solves the FCF growth today's price already requires (an implied requirement, not a forecast or price target), fundamentals, technicals, SEC filings with primary text and sections, insider activity, dividends, earnings, events, multi-symbol compare, and search/coverage. REST routes are authenticated GET /v2/* calls. Hosted MCP is at https://api.stockcontext.com/mcp and uses X-API-Key.
Agent rules: preserve envelopes, freshness, as_of, market_status, units, signs, unavailable states, and calls/tools used. Treat freshness: "unsupported" as a fact. Do not invent missing ETF holdings or private-beta ownership/governance/segment facts; report unavailable reasons when a subfamily is absent. Do not invent consensus, price targets, transcripts, news, options, execution, or investment advice. Branch on error.code. Non-retryable: UNAUTHORIZED, PLAN_UPGRADE_REQUIRED, SYMBOL_INVALID, SYMBOL_UNKNOWN, PARAM_INVALID, PREREQUISITE_MISSING; retryable: RATE_LIMITED, UPSTREAM_UNAVAILABLE, AUTH_UPSTREAM_UNAVAILABLE. Retry only when error.retryable is true and honor Retry-After on 429. Every response that passes key verification carries X-RateLimit-Limit/Remaining/Reset, including 4xx; throttle as Remaining approaches 0. /v2/compare takes 2-12 symbols; /version is a root liveness marker (not under /v2).
# Core concepts (/docs/concepts)
StockContext is built on four contracts: authenticated `GET /v2/*` routes, one response envelope, explicit freshness, and fields with documented units. A client that respects them handles every route the same way, with no per-endpoint special cases. Each contract has one owner page that states it in full.
Success is exactly `{ "data": ... }`; failure is exactly `{ "error": { "code", "message", "retryable" } }`. Never parse a raw object at the top level.
`freshness`, `as_of`, and `market_status` tell you how current a response is before you show or cache it.
`_usd` is whole U.S. dollars, `_pct` is a percentage rather than a decimal, and signs are meaningful.
5,628 US-listed symbols, plus which data families do not apply to foreign filers and ETFs.
Two design choices follow from those contracts and surprise people often enough to call out here.
## Unsupported is not an error [#unsupported-is-not-an-error]
When a route does not apply to a symbol or asset type, you get HTTP 200 with a normal `data` envelope and `freshness: "unsupported"`, not an error. For example, `/v2/earnings` on an ETF returns 200 with an `unsupported` reason rather than a `4xx`. Branch on `freshness` before you branch on HTTP status.
An unsupported response is still a full envelope: it carries `freshness: "unsupported"` alongside `as_of`, `market_status`, `cache_age_seconds` (int; `0` when freshly computed), and a machine-readable reason. Parse it exactly like any other 200.
## Nulls and omissions are intentional [#nulls-and-omissions-are-intentional]
StockContext does not fill unknowns with fake precision. A field that cannot be sourced for a given symbol does not become a guessed number. Instead, structured fields that go missing come back as a small envelope of their own:
```json title="GET /v2/snapshot?symbol=SPY (one field, trimmed)"
{
"pe_ttm": {
"available": false,
"reason": "etf_no_earnings"
}
}
```
That shape is the live SPY snapshot's `fundamentals_quick.pe_ttm`; an ETF has no earnings, so the multiple carries a machine-readable `reason` instead of a value. The same pattern guards financial-company metrics, where cash-flow and EV ratios mislead: snapshot suppresses a bank's FCF yield with `reason: "fcf_yield_suppressed_for_financials"`, while valuation and fundamentals suppress EV multiples, FCF yield, and interest coverage with `reason: "financial_company_metric_not_meaningful"`. Branch on the envelope shape, not on a specific reason string; reasons are endpoint-specific.
Asset type also decides which routes carry data at all. ETFs answer market-data routes like `/v2/snapshot`, `/v2/technicals`, `/v2/price-action`, and `/v2/history`, and return `unsupported` for stock-only routes like `/v2/earnings`, `/v2/fundamentals`, and `/v2/insider`. ETF profiles return fund facts plus source-state-dependent top holdings from SEC Form N-PORT where collected/resolved; otherwise `holdings` returns `{available:false, reason}`. N-PORT is filed with a \~60-day lag.
## REST and MCP share one source of truth [#rest-and-mcp-share-one-source-of-truth]
MCP tools are read-only REST passthroughs. A successful tool call returns the REST `data` envelope unchanged; a failure renders as `[CODE] message (retryable: true|false)`. When the REST/OpenAPI contract and an MCP display disagree, the REST contract wins.
# StockContext docs (/docs)
StockContext gives an AI agent the analyst core it needs for active US-listed stocks and ETFs. The same context comes back over REST and a hosted MCP server: fresh quotes and performance, valuation read against a symbol's own history, fundamentals, technicals, SEC filings with primary text and sections, insider activity, dividends, earnings actuals and company-stated guidance, SEC-derived events, multi-symbol compare, and search and coverage.
The JSON is shaped to act on, not just parse: grades, valuation multiples ranked against the symbol's own history (percentile + label, never against peers or a fair-value target), adaptive valuation anchors, and machine-readable reasons when a metric does not apply. One call answers what this symbol is, how it is trading, how expensive it is against its own history, what its filings and insiders say, and how fresh the data is.
## Start here [#start-here]
Pick the surface that matches how you call APIs. Both authenticate with the same `sctx_` key in the `X-API-Key` header.
Create a key, call `GET /v2/snapshot?symbol=AAPL`, and read the response. Five minutes from zero to a parsed snapshot.
Point an MCP client at `https://api.stockcontext.com/mcp` and call the `stockcontext_*` tools that wrap the same routes. Every tool is a GET, safe to call in any agent loop.
## Coverage [#coverage]
5,628 US-listed symbols on NYSE, NASDAQ, NYSE ARCA, and BATS/Cboe BZX, covering 5,571 stocks and 57 ETFs. Daily price history runs back up to 30 years, bounded by source availability per symbol. Some data families do not apply to every symbol, so check [coverage and gaps](/docs/reference/coverage-and-gaps) for the exact support map before you commit to a route.
## What to call for what [#what-to-call-for-what]
Start narrow with `/v2/search`, `/v2/coverage`, and `/v2/snapshot`, then add depth only when the question needs it. Routes tagged **Paid** need Starter or Builder and return `403 PLAN_UPGRADE_REQUIRED` on a Free key. The [endpoint guide](/docs/guides/choose-endpoints) maps jobs to the smallest route set.
| Need | Routes | Plan |
| -------------------------- | ------------------------------------------------------------------------------------------------- | ----- |
| Discovery | `/v2/search`, `/v2/coverage` | Free |
| First-pass context | `/v2/snapshot` (Free), `/v2/profile` (Paid) | Mixed |
| Valuation and fundamentals | `/v2/valuation` (Free), `/v2/fundamentals`, `/v2/earnings`, `/v2/dividends` (Paid) | Mixed |
| Events | `/v2/calendar` | Paid |
| Price behavior | `/v2/technicals`, `/v2/price-action`, `/v2/history`, `/v2/compare` | Paid |
| SEC and insider context | `/v2/filings`, `/v2/filings/{accession}`, `/v2/filings/{accession}/section/{name}`, `/v2/insider` | Paid |
Free keys can call `/v2/search`, `/v2/coverage`, `/v2/snapshot`, and `/v2/valuation`. Starter and Builder reach every public core route. See [plans and limits](/docs/reference/plans-and-limits) for rate limits and quotas.
Segments, ownership/13F/13D-G, and governance are being validated as private beta families. They are not part of the default public core, public MCP tool list, or agent recipes until their coverage gates are strong enough.
## Where the data ends [#where-the-data-ends]
StockContext is focused on equity context, so an agent always knows exactly where the data ends and never guesses. It does not return:
* real-time exchange-grade or L2/order-book ticks
* options, implied vol, futures, or global-equities coverage
* analyst consensus, price targets, transcripts, or news
* ETF AUM, expense ratios, or holdings where the SEC N-PORT resolver has not collected them
* sector or peer medians (each multiple is ranked against the symbol's own history only)
* a screener or full cross-symbol database
* execution or investment advice
## Next step [#next-step]
New to the API? Start with the [REST quickstart](/docs/quickstart). If you already know the shape of the data, the [API reference](/docs/api-reference/overview) lists every route, param, and field.
# Quickstart (/docs/quickstart)
Make your first REST call from a server. By the end you have a key in your environment, a working `GET /v2/snapshot?symbol=AAPL`, and code that reads both the success and error envelopes.
### Create an API key [#create-an-api-key]
Open the keys page at `https://stockcontext.com/dashboard/keys`, create a key, and copy it before you close the dialog. The raw secret is shown once. Keys start with `sctx_` and travel in the `X-API-Key` header. New keys start on the Free plan: `search`, `coverage`, `snapshot`, and `valuation`; see [plans and limits](/docs/reference/plans-and-limits) for the rest.
Keep StockContext keys server-side only. Never put them in frontend code, browser bundles, public repos, logs, screenshots, support chats, or prompts.
### Set your environment [#set-your-environment]
```bash
export STOCKCONTEXT_API_BASE_URL="https://api.stockcontext.com"
export STOCKCONTEXT_API_KEY=""
```
### Make the first call [#make-the-first-call]
`/v2/snapshot` is the best first call for most apps: it returns quote, performance, 52-week range, quick valuation, sector, and industry in one response.
```bash
curl -sS "$STOCKCONTEXT_API_BASE_URL/v2/snapshot?symbol=AAPL" \
-H "X-API-Key: $STOCKCONTEXT_API_KEY"
```
### Read the success envelope [#read-the-success-envelope]
Success is always wrapped in `data`. The response below is trimmed; the live payload also carries a full `performance` block, a `range_52w` block, and more `fundamentals_quick` fields. The [response model](/docs/reference/response-model) documents the wrapper in full.
```json title="GET /v2/snapshot?symbol=AAPL (trimmed)"
{
"data": {
"symbol": "AAPL",
"name": "Apple Inc",
"currency": "USD",
"as_of": "2026-06-08T13:07:36Z",
"freshness": "end_of_day",
"market_status": "closed",
"cache_age_seconds": 0,
"quote": {
"price": 308.73,
"change_abs": 1.39,
"change_pct": 0.45,
"previous_close": 307.34,
"volume": 65310502
},
"range_52w": {
"high": 316.94,
"low": 194.3,
"position_pct": 92.2,
"drawdown_from_high_pct": -3.03
},
"asset_type": "stock",
"fundamentals_quick": {
"market_cap_usd": 4512101567600,
"pe_ttm": { "value": 36.81, "vs_5y": "near_5y_high" },
"fcf_yield_pct": 2.86,
"dividend_yield_pct": 0.35
},
"sector": "Technology",
"industry": "Consumer Electronics"
}
}
```
The headline multiples are objects, not bare numbers. On `/v2/snapshot`, `pe_ttm`, `ps_ttm`, `pb`, and `ev_ebitda_ttm` each computable value ships as `{ value, vs_5y? }` — read the number at `pe_ttm.value`. `fcf_yield_pct`, `dividend_yield_pct`, the `ttm_*` fields, `shares_outstanding`, and `beta_5y_weekly` stay flat; `market_cap_usd` and `enterprise_value_usd` are flat integers.
`fundamentals_quick.pe_ttm.vs_5y` bands the ratio within the symbol's own five-year history (`near_5y_low`, `below_5y_median`, `near_5y_median`, `above_5y_median`, `near_5y_high`, or the `_3y_` variants). It is not a cheapness verdict and not a peer comparison. `vs_5y` is omitted when the history is too thin or the multiple is non-positive; a negative P/E keeps its value at `.value` and carries a caveat such as `negative_earnings_period`.
Read these before you show or cache the result:
* `data.symbol` is the resolved ticker.
* `data.freshness` is one of `intraday`, `end_of_day`, `stale`, `degraded`, or `unsupported`. See [freshness](/docs/reference/freshness).
* `data.market_status` is `open` or `closed`.
* `data.as_of` is the upstream timestamp when available, otherwise generation time, always UTC ISO-8601.
* `cache_age_seconds` is how long the served data has been cached; `0` means freshly computed.
Every success envelope carries `freshness`, `market_status`, `as_of`, and `cache_age_seconds`. `_usd` fields are whole U.S. dollars and `_pct` fields are percentages, not decimals; [units and fields](/docs/reference/units-and-fields) covers every convention.
### Handle the error envelope [#handle-the-error-envelope]
Failures are always wrapped in `error`. Retry only when `error.retryable` is `true`.
```json
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Please slow request rate and retry.",
"retryable": true
}
}
```
The example above (`RATE_LIMITED`) is retryable; `503 UPSTREAM_UNAVAILABLE` and `503 AUTH_UPSTREAM_UNAVAILABLE` are too. The four errors below are not — fix the cause before you call again:
| Code | HTTP | Fix |
| ----------------------- | ---- | ------------------------------------- |
| `UNAUTHORIZED` | 401 | Set a valid key in `X-API-Key`. |
| `PLAN_UPGRADE_REQUIRED` | 403 | The route needs Starter or Builder. |
| `SYMBOL_INVALID` | 400 | Symbol must match the ticker grammar. |
| `SYMBOL_UNKNOWN` | 404 | Resolve the ticker with `/v2/search`. |
Successful responses carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset`; a `429` carries them too, plus `Retry-After` in seconds. Other error responses do not include them, so read your budget from successes. The full table and retry policy live on the [error codes](/docs/reference/error-codes) page.
## Optional fit checks [#optional-fit-checks]
Run these before you store a user-entered symbol or kick off batch work.
```bash
# Resolve a company name to symbols.
curl -sS "$STOCKCONTEXT_API_BASE_URL/v2/search?query=apple" \
-H "X-API-Key: $STOCKCONTEXT_API_KEY"
# Confirm the symbol is supported and see its asset type.
curl -sS "$STOCKCONTEXT_API_BASE_URL/v2/coverage?symbol=AAPL" \
-H "X-API-Key: $STOCKCONTEXT_API_KEY"
```
## Call it from code [#call-it-from-code]
Both clients check the envelope before the HTTP status, because a `4xx`/`5xx` still carries a structured `error`. Keep the key server-side; never ship it to the browser.
```python title="snapshot.py"
import os
import httpx
base_url = os.environ.get("STOCKCONTEXT_API_BASE_URL", "https://api.stockcontext.com")
api_key = os.environ["STOCKCONTEXT_API_KEY"]
with httpx.Client(
base_url=base_url,
headers={"X-API-Key": api_key},
timeout=10.0,
) as client:
response = client.get("/v2/snapshot", params={"symbol": "AAPL"})
body = response.json()
if "error" in body:
error = body["error"]
raise RuntimeError(
f"{error['code']}: {error['message']} (retryable={error['retryable']})"
)
response.raise_for_status()
data = body["data"]
# Headline multiples are {value, vs_5y} objects: read the number at .value.
pe = data["fundamentals_quick"]["pe_ttm"]["value"]
print(data["symbol"], data["freshness"], data["as_of"], data["quote"]["price"], pe)
```
```ts title="snapshot.ts"
type NoxstockEnvelope =
| { data: T }
| { error: { code: string; message: string; retryable: boolean } };
// A headline multiple is { value, vs_5y?, caveat? } when computable.
// Missing inputs return an { available: false, reason } object instead.
type QuickMultiple = { value: number; vs_5y?: string; caveat?: string };
type Snapshot = {
symbol: string;
freshness: string;
as_of: string;
quote?: { price?: number };
fundamentals_quick?: { pe_ttm?: QuickMultiple };
};
const baseUrl = process.env.STOCKCONTEXT_API_BASE_URL ?? "https://api.stockcontext.com";
const apiKey = process.env.STOCKCONTEXT_API_KEY;
if (!apiKey) {
throw new Error("Missing STOCKCONTEXT_API_KEY");
}
async function noxstockGet(path: string, params: Record) {
const url = new URL(path, baseUrl);
url.search = new URLSearchParams(params).toString();
const response = await fetch(url, {
headers: { "X-API-Key": apiKey },
signal: AbortSignal.timeout(10_000),
cache: "no-store",
});
const body = (await response.json()) as NoxstockEnvelope;
if ("error" in body) {
const error = new Error(`${body.error.code}: ${body.error.message}`) as Error & {
retryable?: boolean;
};
error.retryable = body.error.retryable;
throw error;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status} without a StockContext error envelope`);
}
return body.data;
}
async function main() {
const snapshot = await noxstockGet("/v2/snapshot", { symbol: "AAPL" });
const pe = snapshot.fundamentals_quick?.pe_ttm;
const peValue = typeof pe === "number" ? pe : pe?.value;
console.log(snapshot.symbol, snapshot.freshness, snapshot.as_of, snapshot.quote?.price, peValue);
}
main();
```
## Next steps [#next-steps]
Most apps call `GET /v2/valuation?symbol=AAPL` next; it pairs with snapshot and is available on Free keys. It ranks each multiple against the symbol's own 3/5/10-year history (a percentile plus a label), never against peers or a fair-value target. If your question is not valuation, the [endpoint guide](/docs/guides/choose-endpoints) maps jobs to routes.
Pick the smallest route set for your job.
Build retry behavior around `error.retryable`.
Connect MCP if you are building agents.
# Agent instructions (/docs/agents/instructions)
Paste this block into the system or developer prompt of any agent that can reach StockContext over REST or MCP. It is the one place the operating rules live; recipes and the skill file point back here. Set `STOCKCONTEXT_API_KEY` in the agent's environment first ([create a key](https://stockcontext.com/dashboard/keys)).
```text title="noxstock-system-prompt.txt"
You use StockContext for US-listed stock and ETF context. It covers NYSE and NASDAQ
listings only: no options, futures, crypto, FX, or non-US equities.
Connection
- Hosted MCP: connect to https://api.stockcontext.com/mcp and send the header
X-API-Key: $STOCKCONTEXT_API_KEY. Confirm the stockcontext_* tools loaded, then
smoke-test stockcontext_snapshot with symbol "AAPL". MCP is read-only and calls REST.
- REST: base URL https://api.stockcontext.com, same X-API-Key header on every request.
When MCP and REST disagree, REST/OpenAPI is the source of truth.
Envelope
- Success is {"data": ...}. Failure is {"error": {"code", "message", "retryable"}}.
- A 200 with freshness "unsupported" plus a machine-readable reason is a real
answer, not an error. Report it as "not available from StockContext", do not retry it.
Default call sequence
- If the user names a company or fund, call search first to resolve the symbol.
- Call coverage before you store, batch, or deeply analyze a symbol. For
store-backed SEC facts, branch on support.facts.available_now before calling.
- Call snapshot for most single-symbol questions, then add only what the question
needs: valuation, fundamentals, facts, events, compare (2-12 tickers),
filings list -> filing -> filing section or diff for SEC text, insider for
Form 4/Form 144 activity, technicals + price-action for trend and timing,
history only when you need a time series.
Plan access
- A Free key calls search, coverage, snapshot, and valuation only. Anything else
returns PLAN_UPGRADE_REQUIRED (403, not retryable). The snapshot "AAPL" smoke-test
above works on any plan, including Free.
- Starter and Builder keys reach every public core route and MCP tool.
Output rules
- Answer first. Then show the few facts that support it, then freshness, then the
calls you made.
- Preserve every value as returned: units, signs, percentages, fiscal periods,
accessions, filing dates, as_of timestamps, freshness, market_status, and any
notices or null/unavailable reasons. Never round away a sign or invent precision.
- Never invent data StockContext does not return. Facts can be route-supported
but not collected yet; ETF holdings are source-state dependent; and segments,
ownership/13F/13D-G, and governance are private beta, not default public-core
inputs. If a field or subfamily is missing, report its unavailable reason and
stop. Analyst consensus or price targets, earnings-call transcripts, options,
news, and non-US local listings are not available from StockContext.
- A valuation percentile or label (near_5y_low ... near_5y_high, or the _3y_ variants) ranks a
multiple against THE SAME company's own 3/5/10-year history only. Report it as "low" or "high
vs its own multi-year range" and name the window the label uses, never as "cheap," "undervalued,"
"rich," or vs peers/sector/fair value. A multiple can read near_5y_low and still be expensive in
absolute terms. When a change driver or
history_regime is present, say WHY the multiple moved (price vs fundamentals) rather than
quoting the label alone.
- Report priced-in implied growth as "the price is priced for ~X% growth," never as a
forecast, target, or fair value. Cite the sensitivity range, not just one cell, and compare
the implied figure to the company's OWN trailing realized growth (history, not a benchmark).
- Treat an absent or unavailable field as deliberate, not an error. A {available: false, reason}
envelope or an omitted field is a product fact to report, not a gap to fill or retry.
- Summarize SEC risk, MD&A, business, or press-release content only from the filing
section text StockContext returns. Do not blend in news, consensus, the web, or memory.
- Report data; do not advise. No buy/sell/hold verdict, price target, or trade.
Retry rule
- Retry only when error.retryable is true, and honor Retry-After when present.
Do not retry UNAUTHORIZED, PLAN_UPGRADE_REQUIRED, SYMBOL_UNKNOWN, SYMBOL_INVALID,
PARAM_INVALID, PREREQUISITE_MISSING, or an unsupported data state without changing the request.
```
Two facts above are owned elsewhere and worth following: the full [error codes and retry policy](/docs/reference/error-codes), and which [plan reaches which route](/docs/reference/plans-and-limits).
See these rules applied: prompt, exact calls, and a filled answer from real fixtures.
# Agent recipes (/docs/agents/recipes)
Each recipe maps a class of user prompt to the smallest set of StockContext calls and an answer template. The output rules these templates assume (answer first, preserve freshness and signs, no advice) live once in the [agent instructions](/docs/agents/instructions): paste that block into your agent and the templates below fall out of it.
REST paths and MCP tools are listed side by side. MCP arguments use native JSON types: lists are arrays (`["10-K","10-Q"]`), not comma-separated strings. Plan access matters here: a Free key covers the snapshot/valuation recipes in full; `insider`, `compare`, `filings`, `technicals`, and `price-action` need Starter or Builder ([plans and limits](/docs/reference/plans-and-limits)).
## One-symbol brief [#one-symbol-brief]
Serves prompts like "What's going on with AAPL?", "Give me a read on Apple", "Should I be paying attention to NVDA?": a single ticker, no specific sub-topic.
Calls: snapshot is usually enough. Add insider (Starter+) only when the prompt hints at recent officer/director trading; add valuation when it hints at price-versus-worth. On a Free key, skip the activity line and say it was not consulted rather than guessing.
```text
GET /v2/snapshot?symbol=AAPL
GET /v2/insider?symbol=AAPL # only if activity is in scope
```
```text
stockcontext_snapshot({ "symbol": "AAPL" })
stockcontext_insider({ "symbol": "AAPL" }) # only if activity is in scope
```
Template:
```text
Answer:
Setup
-
-
Activity / caveats
-
Freshness
- Snapshot: , as_of , market_status
- Insider: , as_of
Calls used
-
```
Filled with the live AAPL `snapshot` and `insider` fixtures (numbers copied verbatim; the insider rollup returns ten recent transactions and a 90-day summary, trimmed here to the relevant lines):
```text title="aapl-brief.txt"
Answer: AAPL is trading at 307.34, down 1.25% on the day but up 53.8% over the past
year (+28.01% vs SPY), sitting near the top of its 52-week range.
Setup
- Price 307.34, day change -1.25%. 52-week range 194.30-316.94; position 92.2% of
range, 3.03% below the 52-week high.
- 1-year +53.8%, vs SPY +28.01%. Sector: Technology, Consumer Electronics.
- Trailing P/E 36.81 (vs_5y near_5y_high), FCF yield 2.86%, dividend yield 0.35%.
Activity / caveats
- Insider 90-day Form 4 rollup: net direction "selling". 0 open-market buys vs 13
open-market sells, net_shares_market_only -397,759, net_value_usd -$111,705,105.08.
Most recent sale: Arthur D Levinson (Director), 50,000 shares at 311.02 on 2026-05-27,
10b5-1 plan not disclosed (ten_b5_1 is {available: false, reason:
"trading_plan_not_disclosed"}, undisclosed, not the same as "no plan").
- Notice: recent_transactions_limited_to_10. The full rollup scans the latest 200
Form 4 filings; only the 10 most recent transactions are itemized.
Freshness
- Snapshot: end_of_day, as_of 2026-06-05T20:00:00Z, market_status closed.
- Insider: end_of_day, as_of 2026-06-06T07:17:18Z.
Calls used
- snapshot, insider
```
Note the two `as_of` timestamps differ: the snapshot and the Form 4 rollup were computed on different days. Surface both rather than collapsing them into one. The snapshot's quick P/E ships as an object, `pe_ttm: {value: 36.81, vs_5y: "near_5y_high"}`; read the number at `.value` and report the `vs_5y` band as a position within AAPL's own five-year range, never as a cheap/expensive verdict.
## Valuation check [#valuation-check]
Serves "Is AAPL expensive?", "Is NVDA cheap right now?", "How does the multiple look?". Snapshot gives price context; valuation gives the multiples and where each ranks against the symbol's own history (a window-scoped label like `near_5y_low`, plus a price-vs-fundamentals decomposition). Report the rank as low/high vs the symbol's own range, never as a cheap/undervalued verdict. Skip fundamentals unless the user asks *why* the multiple is what it is.
```text
GET /v2/snapshot?symbol=NVDA
GET /v2/valuation?symbol=NVDA
```
```text
stockcontext_snapshot({ "symbol": "NVDA" })
stockcontext_valuation({ "symbol": "NVDA" })
```
Template:
```text
Answer:
Why
- valuation_context: anchor , value , label
- vs_own_history: percentile across the included windows, median anchor
- change: driver , e.g. fundamentals_outran_price (say WHY the multiple is where it is)
-
Caveats
-
Freshness
- Valuation: , as_of , market_status
Calls used
- snapshot, valuation
```
`change` is present only on `pe_ttm`, `ps_ttm`, and the anchor multiple; its absence on `pb` or `ev_ebitda_ttm` is expected, not missing data. The `vs_own_history.percentile` and `label` rank each multiple against the symbol's own 3/5/10-year history only — never peers, a sector median, or a fair-value target. A low percentile or `near_5y_low` is not a "cheap" verdict, and a falling multiple can mean fundamentals rose rather than price fell (read `change.driver`, e.g. `fundamentals_outran_price`).
ETFs do not get valuation multiples: `/v2/valuation` for an ETF returns HTTP 200 with `freshness: "unsupported"`. Treat that as the answer, not a failure.
## Compare a basket [#compare-a-basket]
Serves "Compare NVDA, AMD, and INTC", "How do these three stack up?". One `compare` call returns an aligned row per symbol. If the user wants more, deepen only the symbol they fixate on.
`symbols` takes 2-12 tickers; 13 or more returns `PARAM_INVALID`. The default field set is `price,pct_1y,vs_spy_pct_1y,pe_ttm,rsi_14,trending`; any field a symbol cannot supply is listed in `fields_omitted_by_symbol`.
```text
GET /v2/compare?symbols=NVDA,AMD,INTC
```
```text
stockcontext_compare({ "symbols": ["NVDA", "AMD", "INTC"] })
```
Template:
```text
Answer:
Scorecard
| Symbol | | | |
| --- | ---: | ---: | ---: |
| | | | |
Notes
- fields_requested:
- fields_omitted_by_symbol:
Freshness
- Compare: , as_of , market_status
Calls used
- compare
```
`compare` returns multiples as bare floats: it unwraps snapshot's `{value, vs_5y}` to the number, so a compare row has no per-multiple `vs_5y` label. A negative `pe_ttm` means trailing losses, not a cheap multiple: keep the sign and say so. Any row carrying a non-meaningful multiple also gets a per-row `flags` array (e.g. `flags: ["pe_ttm_negative_not_meaningful"]`); read it and surface the caveat. `flags` is omitted when no caveats apply.
## SEC risk review [#sec-risk-review]
Serves "What are the risks in Apple's latest 10-K?", "Summarize Tesla's risk factors". Resolve the symbol, list filings, pick the latest 10-K's accession, then pull the section text and summarize *only* that text.
```text
GET /v2/filings?symbol=AAPL&form=10-K&limit=3
GET /v2/filings/0000320193-25-000079
GET /v2/filings/0000320193-25-000079/section/risk_factors
```
```text
stockcontext_filings_list({ "symbol": "AAPL", "forms": ["10-K"], "limit": 3 })
stockcontext_filings_get({ "accession": "0000320193-25-000079" })
stockcontext_filings_section({ "accession": "0000320193-25-000079", "name": "risk_factors" })
```
Template:
```text
Answer: <1-2 sentence summary drawn from the section text only>
Key risks from the section
-
-
-
Source
- Form