# 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
, accession , filed , period - Section:
, char_count Freshness - Filing/section: , as_of Calls used - filings_list, filings_get, filings_section ``` The risk\_factors section for that AAPL accession returns `char_count` 68069, far more than fits one answer. Summarize the themes (macro and tariff exposure, supply-chain concentration, competition, legal and regulatory) and say the text was condensed. Do not add a single fact that is not in the returned text: no news, no consensus, no stock move, no memory. The 1,125 foreign private issuers (\~20% of the universe) file 20-F/40-F/6-K, so `/v2/filings` returns `unsupported` for them. See [coverage and gaps](/docs/reference/coverage-and-gaps). ## Insider scan [#insider-scan] Serves "Any notable insider activity in AAPL?", "Are executives selling?". One `insider` call returns the trailing 90-day Form 4 rollup plus recent transactions. `limit` ranges 1-200 (default 10). The rollup scans the latest 200 Form 4 filings; if it truncates, `summary_90d.notices` says so. ```text GET /v2/insider?symbol=AAPL&limit=20 ``` ```text stockcontext_insider({ "symbol": "AAPL", "limit": 20 }) ``` Template: ```text Answer: 90-day Form 4 summary - Open-market: buys, sells, net_shares_market_only , net_value_usd - All activity: total, net_shares_all_kinds , by kind - 10b5-1 plan share: % - Notices: Recent transactions - : (), , , , 10b5-1 Freshness - Insider: , as_of Calls used - insider ``` Some transactions have no price: gifts and exercises return `value_usd` as `{ "available": false, "reason": ... }`. Keep the reason; do not impute a dollar value. `ten_b5_1` is three-state and never a bare `false`: it is either `true` (the filing affirmatively cites a Rule 10b5-1 plan) or the envelope `{ "available": false, "reason": "trading_plan_not_disclosed" }`. Read that envelope as undisclosed, not as "discretionary" or "no plan": print "not disclosed", never "false". Selling pressure in the rollup is not a sell signal; report it, do not advise on it. ## Technical timing [#technical-timing] Serves "Is AAPL overbought?", "Is the trend extended?". Technicals gives indicators and zones; price-action gives volume, extremes, gaps, and drawdowns. Add history only when the user wants the actual series. ```text GET /v2/technicals?symbol=AAPL GET /v2/price-action?symbol=AAPL GET /v2/history?symbol=AAPL&series=close,rsi_14&range=1y&cadence=daily # only for a chart ``` ```text stockcontext_technicals({ "symbol": "AAPL" }) stockcontext_price_action({ "symbol": "AAPL" }) stockcontext_history({ "symbol": "AAPL", "series": ["close", "rsi_14"], "period": "1y", "cadence": "daily" }) ``` Template: ```text Answer: Setup - RSI and zone (note daily vs weekly), distance from moving averages, ADX/trend - 52-week distance, recent drawdown, volume vs average, streak/gap context Caveats - market_status is session state, not execution readiness - Freshness - Technicals: , as_of , market_status - Price action: , as_of , market_status Calls used - technicals, price_action ``` The history tool argument is named `period`; it maps to REST's `range` (whose own legacy alias is also `period`). Do not imply execution readiness or tell the user to enter or exit a trade. For a prompt no recipe covers, pick the smallest route set by job. # Agent skill (/docs/agents/skill) The canonical skill lives at [https://stockcontext.com/SKILL.md](https://stockcontext.com/SKILL.md). The block on this page mirrors that file; the URL is canonical, so point your agent at it rather than copying a snapshot that can drift. The file uses progressive disclosure (a tight body plus links), so it loads cheaply and an agent only fetches the deeper pages when a task needs them. ## Install it [#install-it] A `SKILL.md` with frontmatter `name` and `description` is the portable format across today's coding agents. Drop it into the agent's skills directory and it gets picked up. The honest generic: **add `SKILL.md` to your agent's skills directory.** Exact paths by agent: ```bash mkdir -p .claude/skills/noxstock curl -sS https://stockcontext.com/SKILL.md -o .claude/skills/noxstock/SKILL.md ``` Project-scoped under `.claude/skills/`; use `~/.claude/skills/` for every project. ```bash mkdir -p .codex/skills/noxstock curl -sS https://stockcontext.com/SKILL.md -o .codex/skills/noxstock/SKILL.md ``` Drop it in the agent's skills directory; Codex reads the `name` and `description` frontmatter to decide when it applies. ```bash mkdir -p .gemini/skills/noxstock curl -sS https://stockcontext.com/SKILL.md -o .gemini/skills/noxstock/SKILL.md ``` Place it in the agent's skills directory. If your setup keys off context files instead, reference the URL from there. ```bash mkdir -p .cursor/skills/noxstock curl -sS https://stockcontext.com/SKILL.md -o .cursor/skills/noxstock/SKILL.md ``` Add it to the skills directory, or paste the body into a Cursor rule so it loads with the project. Directory names vary across agents and versions; the constant is "a `SKILL.md` with `name` + `description` in the skills directory." If yours expects a different location, move the file there; the content is identical. ## The file [#the-file] This is the current `SKILL.md`, verbatim: ````md --- name: StockContext description: >- Structured context for US-listed stocks and ETFs (NYSE/NASDAQ): symbol search, snapshot, valuation, fundamentals, earnings, dividends, technicals, price action, history, compare, SEC filings, SEC events, facts, and insider/Form 144 activity. Use over hosted MCP or REST when a task needs quote, performance, valuation, financial, technical, SEC, or insider data for a US ticker. Not for trading, options, real-time L2, or non-US equities. --- # StockContext ## Use it for US-listed stock and ETF context: resolve a company or fund name to a symbol, then pull snapshot, valuation, fundamentals, earnings, dividends, technicals, price action, price history, a 2-12 symbol compare, SEC filings (list, metadata, section text, filing diff), SEC events, facts, or insider and Form 144 activity. Coverage is NYSE, NASDAQ, and their venues (including NYSE ARCA and BATS/Cboe BZX): US-listed stocks and ETFs only. ## Don't use it for Trading or order execution, exchange-grade real-time or L2/order-book data, options, futures, crypto, FX, non-US local listings, analyst consensus or price targets, earnings-call transcripts, news, or investment advice. Store-backed SEC families like `facts` can be route-supported but not yet collected; branch on `/v2/coverage` `available_now` and report payload reasons. Segments, ownership/13F/13D-G, and governance are private beta, not default public-agent inputs. ## Setup Prefer hosted MCP. Set `STOCKCONTEXT_API_KEY` first (create a key at https://stockcontext.com/dashboard/keys), then add the server: ```json { "mcpServers": { "StockContext": { "url": "https://api.stockcontext.com/mcp", "headers": { "X-API-Key": "$STOCKCONTEXT_API_KEY" } } } } ``` Smoke-test: confirm the `stockcontext_*` tools loaded, then call `stockcontext_snapshot({ "symbol": "AAPL" })`. A success returns `{ "data": ... }` with `freshness` and `as_of`. MCP tool arguments use native JSON types: lists are arrays (`["10-K","10-Q"]`), not comma-separated strings. REST fallback (same key, every request carries the header): ```bash curl -sS "https://api.stockcontext.com/v2/snapshot?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` MCP is read-only and calls REST under the hood. When the two disagree, REST/OpenAPI is the source of truth. Self-hosting instead of hosted MCP? Run the server standalone with `STOCKCONTEXT_API_KEY` and `STOCKCONTEXT_API_BASE_URL` set in the environment: `python -m noxstock.mcp.server --stdio` for stdio, or `python -m noxstock.mcp.server --streamable-http --host 127.0.0.1 --port 8001` for HTTP. ## Plans A Free key calls search, coverage, snapshot, and valuation only; every other route returns `PLAN_UPGRADE_REQUIRED`. Starter and Builder keys reach all public core routes and tools. See https://stockcontext.com/docs/reference/plans-and-limits. ## Default call sequence 1. If the user names a company or fund, call search to resolve the symbol. 2. Call coverage before storing, batching, or deeply analyzing a symbol. 3. Call snapshot for most single-symbol questions. 4. 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 for a time series. ## Output rules - Answer first, then the few supporting facts, then freshness, then the calls you made. - Parse the envelope: success is top-level `data`; failure is top-level `error` with `code`, `message`, and `retryable`. Retry only when `retryable` is true and honor `Retry-After`. - Preserve every value as returned: units, signs, `_pct`, `_usd`, fiscal periods, accessions, filing dates, `as_of`, `freshness`, `market_status`, notices, and null/unavailable reasons. Never round away a sign or invent precision. - Treat `freshness: "unsupported"` and `available: false` reasons as facts. Say "not available from StockContext" instead of guessing; do not retry these states. - Summarize SEC risk/MD&A/business/press-release content only from the returned filing section text. Do not blend in news, consensus, the web, or memory. - Report data; do not advise. No buy/sell/hold verdict, price target, or trade instruction. Keep the API key server-side, never in client code, logs, or prompts. ## Go deeper - Agent recipes (prompt -> calls -> answer): https://stockcontext.com/docs/agents/recipes - Choose the right endpoint: https://stockcontext.com/docs/guides/choose-endpoints - Error codes and retry policy: https://stockcontext.com/docs/reference/error-codes - Plans and limits (why a call returns PLAN_UPGRADE_REQUIRED): https://stockcontext.com/docs/reference/plans-and-limits - Hosted MCP setup: https://stockcontext.com/docs/mcp/hosted - API reference: https://stockcontext.com/docs/api-reference/overview ```` ## Route and tool access [#route-and-tool-access] The skill points agents at the right call; this table is the access map behind it. Each route's MCP tool carries the same name with a `stockcontext_` prefix. | Need | REST route | Plan | | -------------------- | ------------------------------------------------------- | ---------------- | | Company/name lookup | `GET /v2/search?query=apple` | Free | | Coverage check | `GET /v2/coverage?symbol=AAPL` | Free | | First-pass context | `GET /v2/snapshot?symbol=AAPL` | Free | | Valuation | `GET /v2/valuation?symbol=AAPL` | Free | | Profile | `GET /v2/profile?symbol=AAPL` | Starter, Builder | | Statements | `GET /v2/fundamentals?symbol=AAPL&period=quarter` | Starter, Builder | | Earnings | `GET /v2/earnings?symbol=AAPL` | Starter, Builder | | Dividends | `GET /v2/dividends?symbol=AAPL` | Starter, Builder | | Technicals | `GET /v2/technicals?symbol=AAPL` | Starter, Builder | | Price action | `GET /v2/price-action?symbol=AAPL` | Starter, Builder | | History | `GET /v2/history?symbol=AAPL&series=close&range=1y` | Starter, Builder | | Compare 2-12 symbols | `GET /v2/compare?symbols=AAPL,MSFT` | Starter, Builder | | Calendar | `GET /v2/calendar?symbol=AAPL` | Starter, Builder | | Filing list | `GET /v2/filings?symbol=AAPL&form=10-K,10-Q&limit=5` | Starter, Builder | | Filing metadata | `GET /v2/filings/{accession}` | Starter, Builder | | Filing section text | `GET /v2/filings/{accession}/section/{name}` | Starter, Builder | | Insider activity | `GET /v2/insider?symbol=AAPL` | Starter, Builder | | SEC events | `GET /v2/events?symbol=AAPL` | Starter, Builder | | SEC facts | `GET /v2/facts?symbol=AAPL&concepts=revenue,net_income` | Starter, Builder | | Filing diff | `GET /v2/filings/{accession}/diff` | Starter, Builder | Once the skill is installed, these show the call sequences it should produce. # API reference overview (/docs/api-reference/overview) Every product route is a `GET` under `/v2` at `https://api.stockcontext.com`, authenticated with your `sctx_` key in the `X-API-Key` header. ```bash curl "https://api.stockcontext.com/v2/snapshot?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ## Routes [#routes] The access column lists the plans that can call each route. See [plans and limits](/docs/reference/plans-and-limits) for the rate limits and quotas behind those names. | Route | Access | Use | | -------------------------------------------- | ---------------------- | -------------------------------------------------------------- | | `GET /v2/search` | Free, Starter, Builder | Symbol discovery from a name or query. | | `GET /v2/coverage` | Free, Starter, Builder | Supported-universe and asset-type check. | | `GET /v2/snapshot` | Free, Starter, Builder | One-shot market and fundamental context. | | `GET /v2/valuation` | Free, Starter, Builder | Valuation and multiples context. | | `GET /v2/priced-in` | Starter, Builder | Reverse-DCF: the FCF growth the price implies. | | `GET /v2/profile` | Starter, Builder | Company profile and ETF fund facts/holdings where collected. | | `GET /v2/fundamentals` | Starter, Builder | Income, balance sheet, and cash flow rows. | | `GET /v2/facts` | Starter, Builder | Curated SEC fact grid with provenance and PIT queries. | | `GET /v2/earnings` | Starter, Builder | Earnings history and context. | | `GET /v2/dividends` | Starter, Builder | Dividend and payout history. | | `GET /v2/technicals` | Starter, Builder | Technical indicators. | | `GET /v2/price-action` | Starter, Builder | Recent price action and range context. | | `GET /v2/history` | Starter, Builder | Selected historical series. | | `GET /v2/compare` | Starter, Builder | 2–12 symbol scorecard. | | `GET /v2/calendar` | Starter, Builder | Per-symbol upcoming events. | | `GET /v2/filings` | Starter, Builder | Recent SEC filings. | | `GET /v2/filings/{accession}` | Starter, Builder | Filing metadata and section index. | | `GET /v2/filings/{accession}/section/{name}` | Starter, Builder | Full section text. | | `GET /v2/filings/{accession}/diff` | Starter, Builder | Section-level filing diff against the prior comparable filing. | | `GET /v2/insider` | Starter, Builder | Trailing 90-day Form 4 activity and Form 144 notices. | | `GET /v2/events` | Starter, Builder | SEC-derived event labels and filing events. | ## Response shape [#response-shape] Success is `{ "data": ... }`; failure is `{ "error": { "code", "message", "retryable" } }`. The public analyst core exposes 21 routes across the core symbol-varying families plus route-specific filing subresources. Private-beta REST endpoints can remain callable for enabled integrations, but they are not part of the default public route table. Every success payload — and every unsupported payload — carries `freshness`, `as_of`, `market_status`, and `cache_age_seconds` (int; `0` when freshly computed). `freshness` is one of `intraday`, `end_of_day`, `stale`, `degraded`, or `unsupported`. An unsupported response is a full envelope, not an error, and you parse it the same way. ## Query conventions [#query-conventions] * Symbols are US tickers up to 8 characters; dot share classes such as `BRK.B` normalize to `BRK-B`. * Multi-value params are comma-separated: `symbols=AAPL,MSFT`, `form=10-K,10-Q`. * `GET /v2/compare` takes 2–12 distinct symbols. * `GET /v2/filings` accepts up to 4 forms from `10-K`, `10-Q`, `8-K`, `4`, `UPLOAD`, and `CORRESP`. * `GET /v2/history` takes up to 3 series, all on a single cadence. * Timestamps are UTC ISO-8601 unless a field documents otherwise. * Currency fields suffix `_usd`; percent fields suffix `_pct` and are percentages, not decimals. ## Response examples [#response-examples] The JSON in every endpoint page is a real production payload captured against the live API, trimmed where noted. The [error codes](/docs/reference/error-codes) page lists each code, its HTTP status, and whether it is retryable. ## Playground [#playground] Generated endpoint pages include a live playground for manual `GET /v2/*` calls. Paste your key into the masked `X-API-Key` field; keys you enter are sent directly to the API for that request and are never stored. Avoid the playground on shared machines. ## OpenAPI [#openapi] These endpoint pages are generated from the FastAPI OpenAPI spec in `openapi.json`. If a generated schema and a prose guide disagree, trust the OpenAPI schema and the live API, and file a docs bug so the prose can be corrected. # Batch workflows (/docs/guides/batch-workflows) A batch is your own loop, one symbol per request: check coverage first to skip the symbols that cannot answer, fetch a snapshot for the rest, and deepen only the shortlist. The rate that loop runs at is set by your plan's per-minute limit, and the code below reads the rate-limit headers to stay under it. ## Check coverage before you spend the batch [#check-coverage-before-you-spend-the-batch] 1,125 of the 5,628 covered symbols are foreign private issuers that file 20-F/40-F/6-K, so `/v2/filings` and `/v2/insider` return `unsupported` for them; ETFs return `unsupported` for earnings and other stock-only families. Resolve those gaps with one cheap `/v2/coverage` call per symbol before you fan out the expensive routes (see [coverage and gaps](/docs/reference/coverage-and-gaps) for the full universe). Dropping a symbol you know cannot answer is the cheapest request you never make. For a 2–12 symbol comparison, do not hand-roll a batch at all. `/v2/compare?symbols=AAPL,MSFT,NVDA` does it in one call and returns `fields_omitted_by_symbol` for anything a symbol cannot supply. ## Concurrency follows the minute limit [#concurrency-follows-the-minute-limit] Pick your in-flight count from the plan's per-minute limit, then leave headroom, because bursts and retries eat into it. The [plans and limits](/docs/reference/plans-and-limits) page owns the exact numbers; the mapping for batches: | Plan | Minute limit | In-flight | | ------- | -----------: | --------- | | Free | 30/min | 1–2 | | Starter | 60/min | 2–3 | | Builder | 300/min | 5 or more | On **Free**, the plan also bounds *which* routes you can batch: only `/v2/search`, `/v2/coverage`, `/v2/snapshot`, and `/v2/valuation` are reachable. Fanning `fundamentals`, `filings`, or any other route over a Free key returns `403 PLAN_UPGRADE_REQUIRED` per request, before quota ever matters. Then mind the daily quota: 100 requests a day disappears in one watchlist refresh of 30–40 symbols once you add a coverage call and a snapshot call each. Free batch work needs caching or a small list, and there is no header that counts the daily budget down: the `X-RateLimit-*` headers track the minute bucket, so they look healthy right up until the quota 429 lands. Count your own daily requests. A quota 429 is recognizable by its size: `Retry-After` jumps from seconds to hours (the time until the window resets). The quota math lives on [plans and limits](/docs/reference/plans-and-limits). A bounded worker pool keeps you at that in-flight count. Both versions below read `X-RateLimit-Remaining` to slow down early and honor `Retry-After` on a 429 instead of hammering through it. ```python title="batch.py" import asyncio import httpx async def fetch(client, sem, symbol, key): async with sem: # cap in-flight at the semaphore's size res = await client.get( "https://api.stockcontext.com/v2/snapshot", params={"symbol": symbol}, headers={"X-API-Key": key}, ) if res.status_code == 429: retry_after = float(res.headers.get("Retry-After", 5)) if retry_after > 120: # Quota 429: Retry-After is the time to the window reset # (hours, not seconds). Stop the batch instead of sleeping. raise RuntimeError(f"quota exhausted; window resets in {retry_after:.0f}s") await asyncio.sleep(retry_after) return await fetch(client, sem, symbol, key) if int(res.headers.get("X-RateLimit-Remaining", 1)) < 5: await asyncio.sleep(1) # near the ceiling, ease off return symbol, res.json() async def run(symbols, key, in_flight=2): # Free: 1–2, Builder: 5+ sem = asyncio.Semaphore(in_flight) async with httpx.AsyncClient(timeout=15) as client: return await asyncio.gather(*(fetch(client, sem, s, key) for s in symbols)) ``` ```ts title="batch.ts" const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function fetchOne(symbol: string, key: string): Promise<[string, unknown]> { const res = await fetch(`https://api.stockcontext.com/v2/snapshot?symbol=${symbol}`, { headers: { "X-API-Key": key }, }); if (res.status === 429) { const retryAfter = Number(res.headers.get("Retry-After") ?? 5); if (retryAfter > 120) { // Quota 429: Retry-After is the time to the window reset (hours). throw new Error(`quota exhausted; window resets in ${retryAfter}s`); } await sleep(retryAfter * 1000); return fetchOne(symbol, key); } if (Number(res.headers.get("X-RateLimit-Remaining") ?? 1) < 5) { await sleep(1000); // near the ceiling, ease off } return [symbol, await res.json()]; } // Plain promise pool. No dependency. Free: inFlight 1–2, Builder: 5+. async function run(symbols: string[], key: string, inFlight = 2) { const queue = [...symbols]; const results: [string, unknown][] = []; const workers = Array.from({ length: inFlight }, async () => { let symbol: string | undefined; while ((symbol = queue.shift())) results.push(await fetchOne(symbol, key)); }); await Promise.all(workers); return results; } ``` The 120-second cutoff that separates a minute 429 from a quota 429 is a heuristic, not a contract; pick one threshold and reuse it everywhere so this loop and the one in [errors and retries](/docs/guides/errors-and-retries) agree. For the full retry semantics (attempt caps, total-wait budget, retrying only `retryable: true`), wrap each request with that loop. The minimal `Retry-After` handling above keeps a batch from drowning, but it is not the complete policy. ## SEC routes do not belong in a tight loop [#sec-routes-do-not-belong-in-a-tight-loop] Filing and insider routes read from EDGAR and can be slow cold, so they are the wrong thing to fan out across a list. Run them in a background job, cache filing metadata and section text hard (it does not change), and only pull them for the shortlist a user actually opened. The same applies to `/v2/insider`, which scans the latest 200 Form 4 filings per symbol. ## Where batches go wrong [#where-batches-go-wrong] * Calling `fundamentals`, `filings`, and `insider` for every watchlist row instead of snapshot-first, then shortlisting. * Treating `PLAN_UPGRADE_REQUIRED` as retryable and looping on it mid-batch. * Continuing to send after a 429 instead of waiting `Retry-After`. * Retrying `unsupported` ETF or foreign-issuer states; they are stable `200`s, not failures. * Making every missing field look like a zero. Carry the `reason` through. Caching, key safety, and SEC loading states around the loop. The one-call scorecard for 2–12 symbols. # Build an equity agent (/docs/guides/build-an-agent) An agent that answers "Is AAPL extended here?" needs two facts: where the price sits in its recent range, and what the momentum indicators say. That is two calls, not ten. This page traces that question end to end, then shows the two runtimes that carry it: hosted MCP and a REST-backed tool function. ## The tool-selection policy [#the-tool-selection-policy] Start narrow. The first call is almost always `/v2/snapshot`: it carries price, 52-week position, performance, and quick fundamentals in one payload. Add exactly one call per dimension the question opens, and stop when the question is answered. "Is AAPL extended here?" has two dimensions: position in range (snapshot covers it) and momentum (snapshot does not). So the agent calls `snapshot`, reads that AAPL is near its 52-week high, then adds `technicals` for RSI, MACD, and trend strength. It does not call `valuation`, `fundamentals`, or `history`: none of them answer "extended." For routing other question shapes to the right starting endpoint, see [choose endpoints](/docs/guides/choose-endpoints). ## A worked trace [#a-worked-trace] **User:** Is AAPL extended here? **Call 1, snapshot.** The agent asks for position in range first. ```json title="GET /v2/snapshot?symbol=AAPL (trimmed)" { "data": { "symbol": "AAPL", "as_of": "2026-06-05T14:19:06Z", "freshness": "intraday", "market_status": "open", "cache_age_seconds": 0, "quote": { "price": 314.86, "change_pct": 1.17, "previous_close": 311.23 }, "performance": { "pct_1m": 9.62, "pct_ytd": 15.06, "pct_1y": 54.06, "vs_spy_pct_1y": 25.56 }, "range_52w": { "high": 316.94, "low": 194.3, "position_pct": 95.3, "drawdown_from_high_pct": -1.8 }, "asset_type": "stock" } } ``` The full payload also returns `fundamentals_quick`, `sector`, and more, trimmed here to the fields that answer the question. AAPL sits at 95.3% of its 52-week range, 1.8% off the high, up 54% on the year. That reads extended on price. The agent needs momentum to confirm, so it makes one more call. **Call 2, technicals.** Same symbol, one new dimension. ```json title="GET /v2/technicals?symbol=AAPL (trimmed)" { "data": { "symbol": "AAPL", "as_of": "2026-06-05T14:19:11Z", "freshness": "intraday", "market_status": "open", "momentum": { "rsi_14": 66.61, "rsi_zone": "neutral", "rsi_14_weekly": 71.17, "rsi_14_weekly_zone": "overbought", "macd": { "histogram": -0.48, "above_signal": false } }, "trend_strength": { "adx_14": 45.73, "trending": true }, "volatility": { "bollinger_20": { "percent_b": 0.76 } } } } ``` The live response also carries `moving_averages` and `relative_strength`. Daily RSI is 66.6 (neutral, not yet overbought), but weekly RSI is 71.2 (overbought) and MACD has rolled below its signal line. ADX 45.7 confirms a strong trend is in force. Two calls, enough to answer. **Final answer.** The agent answers first, then cites the data and the freshness it stood on: > Yes, AAPL looks extended on the longer timeframe but not at a daily-chart extreme. It trades at 314.86, 1.8% below its 52-week high and at 95.3% of its 52-week range, up 54% over the past year. Weekly RSI is overbought at 71.2 and MACD has crossed below its signal line, while daily RSI is still neutral at 66.6 and ADX 45.7 shows the uptrend intact. > > As of 2026-06-05 14:19 UTC, market open, intraday data. Calls used: snapshot, technicals. This is context, not advice. That last paragraph is not optional. The single instruction that produces it (answer first, cite freshness and units, never give buy/sell advice, never invent data StockContext does not return) lives in [agent instructions](/docs/agents/instructions); paste that block into your system prompt. ## Runtime A: hosted MCP [#runtime-a-hosted-mcp] If your agent runs in Claude, Cursor, Codex, or any MCP client, connect the hosted server once and the model calls the tools directly. Setup (endpoint, auth header, smoke test) is on [MCP hosted](/docs/mcp/hosted). With it connected, the trace above is two tool calls: ```json { "tool": "stockcontext_snapshot", "arguments": { "symbol": "AAPL" } } { "tool": "stockcontext_technicals", "arguments": { "symbol": "AAPL" } } ``` Each tool returns the same `{ "data": ... }` envelope you saw above, unchanged. Tool arguments use native JSON types, so list-valued arguments are arrays, not comma strings: for example `stockcontext_filings_list({ "symbol": "AAPL", "forms": ["10-K", "10-Q"] })`. A tool failure renders as `[CODE] message (retryable: true|false)`; branch on that string the same way you would branch on `error.retryable` over REST. ## Runtime B: REST-backed tool function [#runtime-b-rest-backed-tool-function] If you run your own agent loop, each tool is a function that calls REST and returns the parsed `data`. The detail to get right is auth: the key goes in the `X-API-Key` header, server-side, never in a prompt or client bundle. ```python title="agent_tools.py" import os import httpx BASE = "https://api.stockcontext.com" KEY = os.environ["STOCKCONTEXT_API_KEY"] async def nox_get(route: str, **params) -> dict: """One agent tool per route. Returns parsed `data` or raises on error.""" async with httpx.AsyncClient(timeout=15) as client: res = await client.get( f"{BASE}/v2/{route}", params=params, headers={"X-API-Key": KEY}, ) body = res.json() if "error" in body: err = body["error"] raise RuntimeError(f"[{err['code']}] {err['message']} (retryable: {err['retryable']})") return body["data"] # Expose these to the model as tools: async def snapshot(symbol: str) -> dict: return await nox_get("snapshot", symbol=symbol) async def technicals(symbol: str) -> dict: return await nox_get("technicals", symbol=symbol) ``` ```ts title="agentTools.ts" const BASE = "https://api.stockcontext.com"; const KEY = process.env.STOCKCONTEXT_API_KEY!; type Envelope = | { data: T } | { error: { code: string; message: string; retryable: boolean } }; async function noxGet(route: string, params: Record): Promise { const url = `${BASE}/v2/${route}?${new URLSearchParams(params)}`; const res = await fetch(url, { headers: { "X-API-Key": KEY } }); const body = (await res.json()) as Envelope; if ("error" in body) { const { code, message, retryable } = body.error; throw new Error(`[${code}] ${message} (retryable: ${retryable})`); } return body.data; } // Expose these to the model as tools: export const snapshot = (symbol: string) => noxGet("snapshot", { symbol }); export const technicals = (symbol: string) => noxGet("technicals", { symbol }); ``` The error string mirrors the MCP format on purpose: whichever runtime your agent uses, the model sees `[CODE] message (retryable: ...)` and decides the same way. Wrap `nox_get` with retry only for `retryable: true` codes; the full policy and code are in [errors and retries](/docs/guides/errors-and-retries). ## Where agents go wrong [#where-agents-go-wrong] * Calling `fundamentals` for a valuation question. Start with `valuation`; reach for statements only when the user asks for line items. * Reading a low valuation multiple as "cheap." Each multiple is ranked against the symbol's own 3/5/10-year history only — never peers or a fair-value target — so `near_5y_low` is not a cheapness verdict. And a falling multiple can mean fundamentals rose, not that price fell; read `change.driver` before claiming the price dropped. * Calling `history` to answer "is it extended." Snapshot's `range_52w` and technicals answer it without pulling a series. * Summarizing SEC risk from memory. Use filing section text only. * Retrying `PLAN_UPGRADE_REQUIRED`, `SYMBOL_UNKNOWN`, or any `freshness: "unsupported"` response; none of those change on retry. * Dropping `freshness` and `as_of` from the answer, so the reader cannot tell whether the data is intraday or a week stale. The full operating rules (output format, anti-advice, anti-hallucination) to paste into your system prompt. # Choose endpoints (/docs/guides/choose-endpoints) Resolve a name with `/v2/search`, confirm a symbol with `/v2/coverage`, then call only the family the question needs. Most questions start at `/v2/snapshot`. This page is the table you check before writing a workflow, an agent policy, or a batch job. ## The table [#the-table] Find the user's question, take the route plan, stop there. Every MCP tool is named `stockcontext_` and takes the same arguments as native JSON (lists are arrays, not CSV), so `GET /v2/compare?symbols=NVDA,AMD` becomes `stockcontext_compare({ "symbols": ["NVDA", "AMD"] })`. The mapping is mechanical; the column below names the tool where it isn't obvious. | User question | REST route plan | MCP tool | Plan | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | -------- | | "What ticker is Apple?" | `GET /v2/search?query=apple` | `stockcontext_search` | Free | | "Can I use AAPL?" | `GET /v2/coverage?symbol=AAPL` | `stockcontext_coverage` | Free | | "What's going on with AAPL?" | `GET /v2/snapshot?symbol=AAPL` | `stockcontext_snapshot` | Free | | "Is AAPL expensive?" | `GET /v2/snapshot?symbol=AAPL` then `GET /v2/valuation?symbol=AAPL` | `stockcontext_valuation` | Free | | "Show statements / margins / cash flow." | `GET /v2/fundamentals?symbol=AAPL&period=quarter` | `stockcontext_fundamentals` | Starter+ | | "What does the SEC filing say?" | `GET /v2/filings?symbol=AAPL&form=10-K,10-Q&limit=5` then `GET /v2/filings/{accession}/section/risk_factors` | `stockcontext_filings_list`, `stockcontext_filings_section` | Starter+ | | "Any insider buying or selling?" | `GET /v2/insider?symbol=AAPL&limit=20` | `stockcontext_insider` | Starter+ | | "What yield does it pay?" | `GET /v2/snapshot?symbol=AAPL` (yield is in `fundamentals_quick`) | `stockcontext_snapshot` | Free | | "Dividend history, growth, and health?" | `GET /v2/dividends?symbol=AAPL` | `stockcontext_dividends` | Starter+ | | "Is it extended / trending?" | `GET /v2/technicals?symbol=AAPL` then `GET /v2/price-action?symbol=AAPL` | `stockcontext_technicals`, `stockcontext_price_action` | Starter+ | | "Compare NVDA, AMD, AVGO." | `GET /v2/compare?symbols=NVDA,AMD,AVGO` | `stockcontext_compare` | Starter+ | | "Give me the actual chart series." | `GET /v2/history?symbol=AAPL&series=close,rsi_14&range=1y` | `stockcontext_history` | Starter+ | | "What's coming up for this symbol?" | `GET /v2/calendar?symbol=AAPL` | `stockcontext_calendar` | Starter+ | `Starter+` means a paid plan: any route past the Free four is callable on both Starter and Builder. Free keys can call `/v2/search`, `/v2/coverage`, `/v2/snapshot`, and `/v2/valuation`; everything else returns `403 PLAN_UPGRADE_REQUIRED` on a Free key. The route list, rate limits, and quotas live on [Plans and limits](/docs/reference/plans-and-limits); check there, not here. ## Snapshot or valuation? [#snapshot-or-valuation] These two answer different questions, and the difference decides whether you make one call or two. `/v2/snapshot` is breadth: a quote, multi-window performance, the 52-week range, and a `fundamentals_quick` block. The four headline multiples there — `pe_ttm`, `ps_ttm`, `pb`, `ev_ebitda_ttm` — each ship as a `{value, vs_5y}` object (the number is at `.value`; `vs_5y` is a `near_5y_low` … `near_5y_high` band), while `fcf_yield_pct`, `dividend_yield_pct`, and the `ttm_*` / `shares_outstanding` / `beta_5y_weekly` fields stay flat floats. One call answers "what's going on with this stock?" including a first read on whether each multiple sits high or low in its own range. `/v2/valuation` is depth on those multiples: each one arrives as `{value, vs_own_history}`, where `vs_own_history` carries percentiles across 3y/5y/10y windows, a magnitude anchor median, and a window-scoped `label` (`near_5y_low` through `near_5y_high`). P/E, P/S, and the anchor also carry a `change` block that decomposes the move into price versus fundamentals. That's how you say "low or high *versus its own history*, and why" — which a bare `pe_ttm` of 33 cannot tell you. The label ranks the multiple against the symbol's own past only; it is never a cheap/expensive verdict versus peers or fair value. Add the second call when the user asks whether the price is justified, not only what it is. For "is AAPL up today?" snapshot is the whole answer. For "is AAPL expensive?" you want both, and the [valuation check](/docs/guides/valuation-check) guide walks the two-call path on a Free key. ## Before you store or batch a symbol [#before-you-store-or-batch-a-symbol] Call `/v2/coverage?symbol=` once for user-entered or stored tickers. It tells you the symbol is in the 5,628-symbol universe and which data families it supports: foreign private issuers return `unsupported` for `/v2/filings` and `/v2/insider`, and ETFs skip stock-only families. The full universe and its gaps are documented in [Coverage and gaps](/docs/reference/coverage-and-gaps); skip the check and you find the gap later, mid-answer. ## Where StockContext's data ends [#where-stockcontexts-data-ends] StockContext is focused on US-equity context: quotes, performance, valuation, SEC filings, SEC events, insider Form 4/Form 144 activity, and the fixed ETF tier's N-PORT holdings where collected. Segments, ownership/13F/13D-G, and governance are private beta rather than default public-core inputs. It does not return options or implied vol, analyst price targets or consensus estimates, real-time tick data, news, transcripts, non-US local listings, or execution, so an agent always knows exactly where the data ends and never guesses. If a prompt needs one of those, no route here answers it: say so rather than blending in a guess. Two habits waste quota and degrade answers. Calling every family "just in case" makes an agent more likely to stitch a blended claim from fields it didn't need. And calling `/v2/fundamentals` for a valuation question, or `/v2/history` for a timing question, reaches past the route that already answers it: `/v2/valuation` and `/v2/technicals` respectively. See the coverage → snapshot → valuation path assembled into one answer. # Compare basket (/docs/guides/compare-basket) Call `/v2/compare?symbols=A,B,C` to put 2–12 tickers in one scorecard, then run deeper routes on just the names worth a second look. Compare is a flat projection of snapshot and technicals fields: it ranks nothing and recommends nothing. The point is to skip three or four full snapshot calls and decide which symbols deserve them. ## Symbol count is strict [#symbol-count-is-strict] The endpoint takes 2 to 12 symbols. One symbol is `PARAM_INVALID` (compare needs something to compare against, so use `/v2/snapshot` for a single name). Thirteen or more is `PARAM_INVALID` too; this is enforced and live-verified, not advisory. Both return HTTP 400, and the `PARAM_INVALID` message names the offending parameter and the bound (`\`symbols\` needs at least 2 tickers to compare; got 1`and`\`symbols\` accepts at most 12 tickers; got 13\`), so you can branch on it. Validating the count yourself before the call is still cheaper than a round trip. ## Worked example: NVDA vs AMD vs INTC [#worked-example-nvda-vs-amd-vs-intc] Defaults give you price, 1-year return, 1-year return versus SPY, P/E, RSI, and a trend flag: enough to triage three chip names. The full response (it's small enough to show whole): ```json title="GET /v2/compare?symbols=NVDA,AMD,INTC" { "data": { "as_of": "2026-06-06T07:56:48Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 2662, "fields_requested": [ "price", "pct_1y", "vs_spy_pct_1y", "pe_ttm", "rsi_14", "trending" ], "symbols": [ { "symbol": "NVDA", "price": 205.1, "pct_1y": 46.71, "vs_spy_pct_1y": 20.92, "pe_ttm": 31.23, "rsi_14": 44.01, "trending": false }, { "symbol": "AMD", "price": 466.38, "pct_1y": 303.13, "vs_spy_pct_1y": 277.34, "pe_ttm": 151.8, "rsi_14": 54.55, "trending": true }, { "symbol": "INTC", "price": 99.17, "pct_1y": 396.1, "vs_spy_pct_1y": 370.31, "pe_ttm": -156.07, "rsi_14": 43.72, "trending": true, "flags": ["pe_ttm_negative_not_meaningful"] } ], "fields_omitted_by_symbol": {} } } ``` The MCP form is `stockcontext_compare({ "symbols": ["NVDA", "AMD", "INTC"] })`, same envelope back. Read it in two passes. `fields_requested` tells you exactly which six metrics every row carries, in order; never read a value that isn't in that list. `fields_omitted_by_symbol` is empty here, meaning all six resolved for all three names; when it isn't empty, it maps each symbol to the fields that came back unavailable, and those fields are *dropped from the row entirely* rather than set to null. An absent field is "StockContext couldn't supply this for this symbol," not zero. The scorecard: | Symbol | Price | 1y % | vs SPY 1y | P/E (ttm) | RSI 14 | Trending | | ------ | -----: | ------: | --------: | --------: | -----: | -------: | | NVDA | 205.10 | +46.71 | +20.92 | 31.23 | 44.01 | no | | AMD | 466.38 | +303.13 | +277.34 | 151.80 | 54.55 | yes | | INTC | 99.17 | +396.10 | +370.31 | -156.07 | 43.72 | yes | The honest one-liner from this data: > Over the past year INTC (+396%) and AMD (+303%) crushed NVDA (+47%) and both still register as trending; NVDA carries by far the lowest P/E (31.23), AMD the highest (151.8), and INTC's is negative (-156.07) because it's loss-making on a trailing basis, so P/E isn't a valuation read for it. As of 2026-06-06T07:56:48Z, end of day, market closed. That's a description, not a pick. The negative INTC P/E is the kind of value you must carry through verbatim rather than smooth over: it signals trailing losses, and silently dropping the sign would invent a profitable company. Compare also flags it for you: any row with a non-meaningful multiple carries a `flags` array, here `["pe_ttm_negative_not_meaningful"]`. The array appears only when a caveat applies (other values include `ev_ebitda_ttm_negative_not_meaningful`, and the REIT variants `pe_ttm_reit_pe_distorted` / `ev_ebitda_ttm_reit_pe_distorted`) and is omitted entirely otherwise; read it before treating any multiple in the row as a clean number. ## Fields and defaults [#fields-and-defaults] Omit `fields` and you get the six defaults above: `price`, `pct_1y`, `vs_spy_pct_1y`, `pe_ttm`, `rsi_14`, `trending`. Pass `fields=` (CSV in REST, an array in MCP) to request any subset of these 23: `price`, `pct_1d`, `pct_1m`, `pct_ytd`, `pct_1y`, `vs_spy_pct_1y`, `range_52w_position_pct`, `drawdown_from_high_pct`, `market_cap_usd`, `pe_ttm`, `ps_ttm`, `pb`, `ev_ebitda_ttm`, `fcf_yield_pct`, `dividend_yield_pct`, `beta_5y_weekly`, `rsi_14`, `rsi_zone`, `trending`, `golden_cross_active`, `vs_sma_200_pct`, `distance_from_52w_high_pct`. ETFs in a basket omit the stock-only fields (`market_cap_usd`, `pe_ttm`, and the rest), which then show up under `fields_omitted_by_symbol` for that ticker. That's expected, not a failure. ## Then deepen the finalists [#then-deepen-the-finalists] Compare narrows the field; it doesn't settle it. The mistake is calling snapshot, valuation, fundamentals, and insider for all four names *before* the scorecard: that's the work compare exists to defer. Once two names are left, run the full read on just those. For "which is more reasonably priced?", the [valuation check](/docs/guides/valuation-check) gives each survivor its multiples against five years of its own history, the context the flat `pe_ttm` in this scorecard can't. Take a finalist's P/E and read it against its own 5-year distribution. # Decision brief (/docs/guides/decision-brief) A decision brief is the context a user needs to think about one symbol, with none of the trade advice. The spine is `/v2/coverage` → `/v2/snapshot` → `/v2/valuation`, all on the Free plan; add `/v2/filings` or `/v2/insider` only when the prompt asks for primary-source risk or insider activity. This is a context product, not an advisor: report what the data says, never tell the user to buy, sell, hold, or size anything. This page walks one filled brief end to end. The reusable answer templates (the bullet skeletons you'd paste into an agent) live in [agent recipes](/docs/agents/recipes); this guide is the worked example that shows why each line is there. ## The spine [#the-spine] ```text GET /v2/coverage?symbol=AAPL # confirm support + asset_type, once GET /v2/snapshot?symbol=AAPL # quote, performance, 52w range, fundamentals_quick GET /v2/valuation?symbol=AAPL # multiples vs 5-year history ``` Coverage is the cheap insurance call: it tells you the symbol is in the universe and which families it supports before you build on data that might be `unsupported`. Snapshot and valuation are the brief's body. For a plain "give me a brief," those three are the whole job; `/v2/fundamentals`, `/v2/filings`, and `/v2/insider` are depth you add on request, not by default. The route selection rationale is owned by [Choose endpoints](/docs/guides/choose-endpoints); this page just uses it. ## Worked example: a brief on AAPL [#worked-example-a-brief-on-aapl] The prompt: *"Quick brief on Apple, and is there any unusual insider selling?"* That second clause earns the optional `/v2/insider` call. Two fixtures drive the answer below. From `/v2/snapshot?symbol=AAPL` (quote, performance, and range shown; the live response also returns the full `fundamentals_quick` block, `sector`, and `industry`): ```json title="GET /v2/snapshot?symbol=AAPL (trimmed)" { "data": { "symbol": "AAPL", "as_of": "2026-06-08T13:07:36Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 0, "quote": { "price": 308.73, "change_pct": 0.45, "previous_close": 307.34 }, "performance": { "pct_1m": 7.0, "pct_ytd": 13.62, "pct_1y": 53.8, "vs_spy_pct_1y": 28.01 }, "range_52w": { "high": 316.94, "low": 194.3, "position_pct": 92.2, "drawdown_from_high_pct": -3.03 }, "fundamentals_quick": { "pe_ttm": { "value": 36.81, "vs_5y": "near_5y_high" }, "fcf_yield_pct": 2.86, "dividend_yield_pct": 0.35 } } } ``` The headline multiples are objects, not bare floats: `pe_ttm` (and `ps_ttm`, `pb`, `ev_ebitda_ttm`) ship computable values as `{ value, vs_5y?, caveat? }`, so the number lives at `fundamentals_quick.pe_ttm.value`. `vs_5y` bands the ratio within AAPL's *own* five-year history (`near_5y_high` here); it is not a cheapness verdict and not a peer comparison. `fcf_yield_pct` and `dividend_yield_pct` stay flat floats. From `/v2/insider?symbol=AAPL` (the 90-day rollup; the live response also returns up to 10 `recent_transactions`). `/v2/insider` is a paid route, so this call needs a Starter or Builder key — a Free key gets `403 PLAN_UPGRADE_REQUIRED`: ```json title="GET /v2/insider?symbol=AAPL (summary, trimmed)" { "data": { "symbol": "AAPL", "as_of": "2026-06-06T07:17:18Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 2370, "summary_90d": { "market_activity": { "buy_count": 0, "sell_count": 13, "net_shares_market_only": -397759, "net_value_usd": -111705105.08, "net_direction": "selling" }, "all_activity": { "transactions": 43, "net_shares_all_kinds": 25895, "transactions_by_kind": { "open_market_sale": 13, "derivative_exercise": 22, "tax_withholding": 6, "gift": 2 } }, "unique_insiders": 7, "notices": ["recent_transactions_limited_to_10"] } } } ``` In MCP these are `stockcontext_snapshot({ "symbol": "AAPL" })` and `stockcontext_insider({ "symbol": "AAPL" })`; the envelopes match the REST responses exactly. ### The brief this supports [#the-brief-this-supports] Answer first, then the facts, then a freshness line that's honest about the two routes having different ages, then the calls used: > **Apple is trading near its 52-week high after a strong year, and insiders have been net sellers on the open market over the past 90 days.** At 308.73 (+0.45% on the day) it sits at the 92nd percentile of its 52-week range, 3.03% off the high of 316.94. It's up 53.8% over the year (28.01 points ahead of SPY) and 13.62% year to date, on a trailing P/E of 36.81 (`near_5y_high` in its own five-year range) with a 2.86% free-cash-flow yield and a 0.35% dividend. > > On insiders: over the trailing 90 days there were 13 open-market sales and zero buys across 7 insiders, for net open-market selling of about -$111.7M (`net_shares_market_only` -397,759). Across every Form 4 kind, `all_activity.net_shares_all_kinds` is +25,895 — derivative exercises and grants net positive — so don't conflate the two. The open-market selling is directional, but it's routine for a mega-cap and says nothing about price on its own. > > Freshness: snapshot end\_of\_day, as\_of 2026-06-08T13:07:36Z, market closed. Insider rollup end\_of\_day, as\_of 2026-06-06T07:17:18Z; Form 4 data settles overnight, so it lags the quote by design. > > Calls used: /v2/snapshot, /v2/insider. Three things make that a good brief, not a generated one: * **The freshness line carries both timestamps.** The quote is `end_of_day`; the insider rollup is also `end_of_day` but older, with its own `cache_age_seconds` (2370 here, \~40 minutes). Both carry `freshness`, `as_of`, `market_status`, and `cache_age_seconds`. Collapsing them into one "current as of now" would misrepresent the older half. Cite each route's own `freshness` and `as_of`. The enum and how to detect it are owned by [the freshness reference](/docs/reference/freshness). * **The negative net value keeps its sign, its scope, and its caveat.** `net_value_usd` (`-111705105.08`) and `net_shares_market_only` are *open-market only*; `all_activity.net_shares_all_kinds` (+25,895) covers every Form 4 kind, including derivative exercises and grants. Report the open-market figure as net selling and say so, but never conflate it with the all-kinds total — they can point opposite ways. Dropping the sign, the scope, or the routine-for-a-mega-cap caveat would all mislead. * **The truncation notice is respected.** `notices: ["recent_transactions_limited_to_10"]` means the detail line-item list is capped at the request limit (default 10, flagged `recent_transactions_limited_to_N`). The 90-day *summary* aggregates every Form 4 in the trailing window, scanning up to the latest 200 filings (flagged `scanned_latest_200_filings_only` if it hits that scan cap), so the rollup counts are complete even though the line items aren't. Don't quote "13 sales" as if it were the full picture if the user wants every transaction; point them at the cap. ## When the data says "no answer" [#when-the-data-says-no-answer] If coverage flags a family as `unsupported` (a foreign private issuer for `/v2/filings` and `/v2/insider`, or an ETF for valuation), the brief states that the data isn't available for that symbol and stops. It does not substitute a guess, and it does not retry an `unsupported` response as if it were an outage. A brief that admits a gap is more useful than one that fills it with invention. The reusable brief and answer templates to drop into an agent. # Errors and retries (/docs/guides/errors-and-retries) Every StockContext response is either `{ "data": ... }` or `{ "error": { "code", "message", "retryable" } }`. Read `error.retryable` and do exactly what it says: retry when it is `true`, surface the fix when it is `false`. Do not infer recovery from the HTTP status alone, and never retry an `unsupported` data state: that is a `200` with real data, not an error. ## Retry policy [#retry-policy] This table is the whole decision. `retryable` comes straight off the envelope; the HTTP status is for logging, not branching. | Code | HTTP | Retry? | What to do | | --------------------------- | ---: | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `UNAUTHORIZED` | 401 | No | Fix the `X-API-Key` header; recreate the key if it was revoked. | | `PLAN_UPGRADE_REQUIRED` | 403 | No | The route needs Starter or Builder; stay on Free routes or upgrade. | | `SYMBOL_INVALID` | 400 | No | Fix the ticker format (`^[A-Z0-9-]{1,8}$`, e.g. `BRK-B`). | | `SYMBOL_UNKNOWN` | 404 | No | Resolve the name with `/v2/search` first; the symbol is outside the universe. | | `NOT_FOUND` | 404 | No | The path is not a StockContext route; fix the URL or base path. Distinct from `SYMBOL_UNKNOWN`; carries no rate-limit headers (404s before key verification). | | `PARAM_INVALID` | 400 | No | Fix params. The message names the offending parameter and, for enum/range errors, the allowed values. | | `RATE_LIMITED` | 429 | Yes | Wait the `Retry-After` seconds, then resume slower. | | `UPSTREAM_UNAVAILABLE` | 503 | Yes | Retry with backoff; a cold SEC path may need a loading state. | | `AUTH_UPSTREAM_UNAVAILABLE` | 503 | Yes | Retry shortly; sends `Retry-After: 5`. | | `PREREQUISITE_MISSING` | 409 | No | A required cached prerequisite is missing; call the underlying endpoints first, then retry the request manually. | `PARAM_INVALID` names the offending parameter and lists allowed values, so you can branch on it: a bad `range` returns `range '99z' is not a recognized window; allowed: 10y, 1m, 1y, 2y, 30y, 3m, 5y, 6m, max, ytd.`, and an unsupported `fields` value returns the param name plus the full allowed set. Log the request you sent too: the message tells you which param, but your own log tells you the value. The [error codes reference](/docs/reference/error-codes) carries the message shapes. ## The 429 envelope [#the-429-envelope] A rate-limited response pairs the retryable error body with headers that tell you exactly how long to wait. This is the live fixture: ```http title="HTTP/1.1 429 Too Many Requests" X-RateLimit-Limit: 30 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1780669200 Retry-After: 21 ``` ```json { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please slow request rate and retry.", "retryable": true } } ``` `Retry-After` is in seconds (21 here). Sleep at least that long before the next request for that key; sending sooner earns another 429. ## A retry loop that obeys its own rules [#a-retry-loop-that-obeys-its-own-rules] The loop has to do four things the table implies: retry only on `retryable: true`, prefer the `Retry-After` header over a guessed backoff, cap total attempts so a persistent outage fails fast, and cap total wait so you never sleep for hours. The wait budget must cover a full minute window: a minute-bucket 429 can send `Retry-After` of up to 60 seconds, so a 30-second budget would give up on a limit that was about to clear. Below, three attempts and a 90-second ceiling. ```python title="retry.py" import asyncio import httpx async def get_with_retry( client: httpx.AsyncClient, url: str, api_key: str, max_attempts: int = 3, max_total_wait: float = 90.0, ) -> dict: waited = 0.0 for attempt in range(1, max_attempts + 1): res = await client.get(url, headers={"X-API-Key": api_key}) body = res.json() if "data" in body: return body["data"] err = body["error"] retryable = err["retryable"] if not retryable or attempt == max_attempts: raise RuntimeError(f"[{err['code']}] {err['message']}") # Prefer the server's Retry-After; fall back to exponential backoff. retry_after = res.headers.get("Retry-After") delay = float(retry_after) if retry_after else min(2 ** attempt, 8) if waited + delay > max_total_wait: raise RuntimeError(f"[{err['code']}] wait budget exhausted") waited += delay await asyncio.sleep(delay) raise RuntimeError("retry loop exhausted") ``` ```ts title="retry.ts" type Envelope = | { data: T } | { error: { code: string; message: string; retryable: boolean } }; const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export async function getWithRetry( url: string, apiKey: string, maxAttempts = 3, maxTotalWaitMs = 90_000, ): Promise { let waited = 0; for (let attempt = 1; attempt <= maxAttempts; attempt++) { const res = await fetch(url, { headers: { "X-API-Key": apiKey } }); const body = (await res.json()) as Envelope; if ("data" in body) return body.data; const { code, message, retryable } = body.error; if (!retryable || attempt === maxAttempts) { throw new Error(`[${code}] ${message}`); } // Prefer the server's Retry-After; fall back to exponential backoff. const retryAfter = res.headers.get("Retry-After"); const delayMs = retryAfter ? Number(retryAfter) * 1000 : Math.min(2 ** attempt, 8) * 1000; if (waited + delayMs > maxTotalWaitMs) { throw new Error(`[${code}] wait budget exhausted`); } waited += delayMs; await sleep(delayMs); } throw new Error("retry loop exhausted"); } ``` The budget guard is also your quota protection. When a `429` comes from the plan quota rather than the minute bucket (Free's daily 100, paid monthly quotas), `Retry-After` is the time to the window reset and can be **hours** (a Free daily 429 sends roughly the seconds until midnight UTC). The loop above correctly refuses to sleep that long and fails fast with `wait budget exhausted`; treat that as "stop the job and surface it", not as an error to swallow. [Plans and limits](/docs/reference/plans-and-limits) covers how the two windows show up in the headers. The same loop works behind an MCP client: a tool failure renders as `[CODE] message (retryable: true|false)`, so parse the `retryable` flag out of that string and branch identically. `PREREQUISITE_MISSING` is non-retryable; call the underlying endpoints it depends on first instead of looping. ## Where this goes wrong [#where-this-goes-wrong] * Parsing a success as a raw object instead of reaching into `{ data }`. * Retrying `UNAUTHORIZED` with the same revoked key, or treating `PLAN_UPGRADE_REQUIRED` as an outage. * Retrying `freshness: "unsupported"`; it is a stable `200`, not a transient failure. * Ignoring `Retry-After` and hammering the same key into a second 429. * Retrying forever. Without an attempt cap, a real upstream outage turns one slow request into a hung worker. Drop this retry loop into a cached, key-safe client. # Insider scan (/docs/guides/insider-scan) `/v2/insider` returns one trailing-90-day rollup plus the most recent transaction rows. The work is reading two summaries correctly: `market_activity` is the open-market buy/sell signal, and `all_activity` is every Form 4 kind including exercises and gifts. Mix them up and a quiet quarter looks like a sell-off. ## One call [#one-call] ```python title="insider.py" import httpx r = httpx.get( "https://api.stockcontext.com/v2/insider", params={"symbol": "AAPL", "limit": 10}, # limit 1-200, default 10 headers={"X-API-Key": "sctx_..."}, ) data = r.json()["data"] summary = data["summary_90d"] ``` The rollup scans the latest 200 Form 4 filings in the trailing 90 days. `limit` only caps how many `recent_transactions` rows come back; it does not change the summary math. ## The summary, from a live AAPL response [#the-summary-from-a-live-aapl-response] ```json title="insider.data.summary_90d" { "market_activity": { "buy_count": 0, "sell_count": 13, "net_shares_market_only": -397759, "net_value_usd": -111705105.08, "net_direction": "selling" }, "all_activity": { "transactions": 43, "net_shares_all_kinds": 25895, "transactions_by_kind": { "open_market_sale": 13, "derivative_exercise": 22, "tax_withholding": 6, "gift": 2 } }, "unique_insiders": 7, "officer_share_pct": 42, "director_share_pct": 12, "ten_b5_1_plan_ratio_pct": 0, "notices": ["recent_transactions_limited_to_10"] } ``` Read it straight: * **Open-market:** 0 buys, 13 sells, net -397,759 shares worth -$111.7M. `net_direction` is `selling`. These are the cash trades insiders chose to make, the part most people mean by "insider selling." * **All Form 4 activity:** 43 transactions across 7 insiders. Of those, only 13 are open-market sales; the other 30 are 22 derivative exercises, 6 tax-withholding events, and 2 gifts. `net_shares_all_kinds` is *positive* (+25,895) because exercises add shares, which is why you never quote the all-activity net as a sell figure. * **10b5-1:** `ten_b5_1_plan_ratio_pct` is 0. None of these sales were *affirmatively disclosed* as 10b5-1 plan sales: every row carries `ten_b5_1: { "available": false, "reason": "trading_plan_not_disclosed" }`, meaning the filing made no statement either way. Read a 0% ratio as *undisclosed*, not *discretionary*: you cannot conclude these were unplanned, only that no plan was reported. Plan sales (where `ten_b5_1` is the literal `true`) carry less signal because they were set up in advance. ## Recent rows [#recent-rows] Each row carries a `transaction_code` (the raw Form 4 code) and a plain-English `transaction_kind`. The codes map as `P` open\_market\_purchase, `S` open\_market\_sale, `M` derivative\_exercise, `A` award\_grant, `F` tax\_withholding, `G` gift. Two rows from the live response: ```json title="insider.data.recent_transactions[0..2] (trimmed)" [ { "filing_date": "2026-05-29", "transaction_date": "2026-05-27", "insider_name": "Arthur D Levinson", "insider_role": "Director", "transaction_code": "S", "transaction_kind": "open_market_sale", "shares": 50000, "price_per_share": 311.02, "value_usd": 15551000.0, "shares_remaining": 3764576, "ten_b5_1": { "available": false, "reason": "trading_plan_not_disclosed" }, "accession": "0001140361-26-023363" }, { "filing_date": "2026-05-29", "transaction_date": "2026-05-27", "insider_name": "Arthur D Levinson", "insider_role": "Director", "transaction_code": "G", "transaction_kind": "gift", "shares": 65000, "price_per_share": { "available": false, "reason": "gift_no_market_price" }, "value_usd": { "available": false, "reason": "gift_no_market_price" }, "shares_remaining": 3764576, "ten_b5_1": { "available": false, "reason": "trading_plan_not_disclosed" }, "accession": "0001140361-26-023363" } ] ``` The live response returns 10 rows here (the `limit` default). On the gift row, `price_per_share` and `value_usd` are objects with `available: false` and a `reason`, because a gift has no market price. Treat that as "no dollar figure," never as a $0 trade. Exercises (`derivative_exercise_price_not_disclosed`) and tax withholding (`tax_withholding_no_market_price`) carry the same shape. `ten_b5_1` is three-state and the API never returns a bare `false`: it is the literal `true` when the filing affirmatively discloses a 10b5-1 plan sale, or the envelope `{ "available": false, "reason": "trading_plan_not_disclosed" }` when it does not. The not-disclosed envelope is *silence*, not a "no"; never render it as "insider was NOT on a plan." ## Notices [#notices] `summary_90d.notices` flags anything that bounds the rollup. Two you will see: * `recent_transactions_limited_to_N`: more rows exist than the `limit` you asked for; raise `limit` (up to 200) to see them. * `scanned_latest_200_filings_only`: the 90-day window held more than 200 Form 4 filings, so the rollup covers the most recent 200. Surface this; the summary is complete only up to that cap. Pass `notices` through to the reader verbatim. Dropping it hides the boundary on the numbers. ## A reportable answer [#a-reportable-answer] ```text title="answer" AAPL insider activity, trailing 90 days (as_of 2026-06-05, end_of_day): - Open-market: 0 buys, 13 sells, net -$111.7M across 7 insiders. Direction: selling. - No 10b5-1 plan sales were disclosed (0% plan ratio); the filings made no plan statement. - The other 30 Form 4 events were option exercises, tax withholding, and gifts: routine, not open-market trades. This is a description of filed activity, not a buy or sell signal. ``` That last line is the guardrail. Net selling is not "bearish," net buying is not "bullish": insiders sell for taxes, diversification, and scheduled plans. Describe the activity and its sign; do not convert it to advice. That anti-advice rule lives in the [agent instructions](/docs/agents/instructions). ## Scope [#scope] * `/v2/insider` is Form 4 transactions plus Form 144 intent-to-sell notices. Institutional ownership and 13F holders are private beta, not part of this public-core route. * ETFs and foreign private issuers that file 20-F/40-F/6-K return `unsupported` here, because there are no domestic Form 4 filers to scan. See [coverage and gaps](/docs/reference/coverage-and-gaps). For the prose of a filing (risk factors, MD\&A), walk the section endpoints instead. # Production patterns (/docs/guides/production-patterns) Two things separate a toy integration from a production one: you cache by how fast each data family actually moves, and you keep the key out of anything a user can see. Everything else (retries, quotas) links out to the page that owns it. ## Keep the key server-side [#keep-the-key-server-side] The `X-API-Key` secret belongs in your backend, an agent runtime config, or a secret manager. Never anywhere a user or a model can read it. * No `STOCKCONTEXT_API_KEY` in a browser bundle, mobile app, public repo, prompt, log line, or screenshot. * Proxy client calls through your own backend; the key never reaches the browser. * The raw secret is shown once at creation. Store it in a secret manager, not a config file you commit. New keys come from the [dashboard](https://stockcontext.com/dashboard/keys). * On suspected leak, recreate the key and swap it in. Revocation takes effect within seconds (the auth cache holds a verified key for at most 15 seconds). * Do not cache `UNAUTHORIZED` or `PLAN_UPGRADE_REQUIRED` responses as if they were data; a fixed key or upgrade should take effect immediately. ## Cache by data family [#cache-by-data-family] A snapshot during market hours goes stale in seconds; a 10-K section never changes. Cache each family on its own clock. These are starting points to tune against your traffic, not API contracts: | Data family | Suggested cache | | -------------------------------------------- | --------------------------------------------------------- | | Search and coverage | Hours to days | | Snapshot during market hours | 30–120 seconds | | Snapshot after close | 5–30 minutes | | Valuation, fundamentals, earnings, dividends | 1–24 hours | | Technicals, price action, history | 5–30 minutes for active views; longer for offline reports | | Filing metadata and section text | Days to indefinite | | Insider activity | 1–24 hours | | Calendar | 1–24 hours | Only `search`, `coverage`, `snapshot`, and `valuation` are reachable on a Free key; every other family in this table needs Starter or Builder and returns `403 PLAN_UPGRADE_REQUIRED` on a Free key. See [plans and limits](/docs/reference/plans-and-limits) for the route gating. Two hooks make these TTLs self-tuning. Every payload carries `cache_age_seconds` (0 means freshly computed), so you can see how warm the served data already was. And every response carries `freshness`: if it comes back `stale` or `degraded`, shorten your own TTL or show the caveat rather than caching it long. ### Cache keys [#cache-keys] Key by route plus normalized params. A snapshot key is as simple as: ```text nox:snapshot:AAPL ``` Normalize before you build the key: uppercase the symbol, sort comma-joined multi-value params (`series`, `form`, `fields`), and apply defaults so `range=1y` and an omitted range hit the same entry. A history key with sorted series looks like `nox:history:AAPL:range=1y:series=close,rsi_14`. The REST filings param is `form` (comma-joined, e.g. `form=10-K,10-Q`); the MCP `filings_list` tool calls the same argument `forms` (an array). If two plans or keys can see different data or quotas, scope the key with `nox:snapshot:AAPL:builder` or a per-key hash. A shared cache that ignores plan scope can serve a Free-route miss as a hit, or leak one tenant's warmer data to another. ## Retries and rate limits [#retries-and-rate-limits] Wrap every call in the retry loop that honors `Retry-After`, caps attempts, and retries only `retryable: true`. The code lives in [errors and retries](/docs/guides/errors-and-retries); use it directly rather than reimplementing it here. Every authenticated response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix epoch seconds); watch `Remaining` against `Limit` to back off before you hit a 429. The per-plan minute and quota numbers live on [plans and limits](/docs/reference/plans-and-limits). ## SEC routes need a loading state [#sec-routes-need-a-loading-state] Filing and insider routes read from EDGAR. A cold fetch for a heavy filer can be slow, so do not block a page render on it. Run those routes in a background job or behind a loading state, and cache the result hard, because filing section text is effectively immutable. ```text GET /v2/filings?symbol=AAPL&form=10-K,10-Q&limit=5 GET /v2/filings/{accession} GET /v2/filings/{accession}/section/risk_factors ``` ## Preserve the honest fields [#preserve-the-honest-fields] Whatever your UI shows, carry these through from the response so the answer stays truthful: * `freshness`, `as_of`, `market_status`, and `cache_age_seconds`. * `reason` and `available: false` on unsupported families, `fields_omitted_by_symbol` from compare, and `summary_90d.notices` from insider rollups. * Units and signs: `_usd`, `_pct`, share counts, drawdowns, cash-flow signs, insider net values. Hiding a `stale`, `degraded`, or `unsupported` state makes the number look more certain than it is. Surface it. Run many symbols with bounded concurrency and no bulk endpoint. Per-plan minute limits, monthly quotas, and route gating. # Reverse DCF: what is priced in (/docs/guides/reverse-dcf) `/v2/valuation` tells you where a multiple sits in its own history. It does not tell you what growth the *current* price requires to make sense. `/v2/priced-in` answers that: it runs a reverse discounted-cash-flow **model** and solves for the free-cash-flow growth rate today's enterprise value already embeds. The MCP tool is `stockcontext_priced_in`. It is a paid route (Starter and Builder). The single most important sentence in this guide: the implied growth is **what the price REQUIRES, not what the company will do.** It is not a forecast, not a fair value, not a price target, and not a buy/sell signal. There is no price field anywhere in the response — the tool inverts the question on purpose (price in, required growth out) so the number cannot be quoted as a target. Read it as *"the price is priced for \~X% growth,"* then judge whether that requirement is plausible. ## The shape [#the-shape] ```json title="GET /v2/priced-in?symbol=NVDA (trimmed)" { "data": { "symbol": "NVDA", "currency": "USD", "as_of": "2026-06-08T13:07:48Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 0, "shape_kind": "reverse_dcf", "basis": "fcf_ttm", "anchor": { "ttm_fcf_usd": 119076000000, "enterprise_value_usd": 4915706000000, "discounted_against": "enterprise_value" }, "model_assumptions": { "discount_rate_pct": 10.0, "horizon_years": 10, "terminal_growth_pct": 2.5, "growth_fade": "linear_to_terminal", "all_fixed_defaults": true }, "implied": { "growth_required_pct": { "value": 31.0, "means": "To justify today's enterprise value at a 10.0% discount over 10 years (fading to 2.5% terminal), free cash flow must compound at about 31.0%/yr. This is what the PRICE REQUIRES, not a forecast.", "solver": "bisection", "converged": true } }, "sensitivity": { "metric": "growth_required_pct", "discount_rate_pct_rows": [8.0, 10.0, 12.0], "terminal_growth_pct_cols": [2.0, 2.5, 3.0], "grid": [ [23.9, 21.9, 19.8], [32.5, 31.0, 29.3], [40.0, 38.7, 37.4] ] }, "historical_realized_growth": { "fcf_cagr": { "3y": { "value": 215.05, "base_effect": "low_base_period", "base_note": "the 3y-ago fcf base was about 3% of today's, so the CAGR is mechanically inflated by the small starting value" }, "5y": { "value": 90.92, "base_effect": "low_base_period", "base_note": "the 5y-ago fcf base was about 4% of today's, so the CAGR is mechanically inflated by the small starting value" }, "10y": { "available": false, "reason": "insufficient_statement_history" } }, "revenue_cagr": { "3y": { "value": 111.03, "base_effect": "low_base_period", "base_note": "the 3y-ago revenue base was about 11% of today's, so the CAGR is mechanically inflated by the small starting value" }, "5y": { "value": 72.34, "base_effect": "low_base_period", "base_note": "the 5y-ago revenue base was about 7% of today's, so the CAGR is mechanically inflated by the small starting value" }, "10y": { "available": false, "reason": "insufficient_statement_history" } }, "window_disagreement": { "detected": true, "metric": "fcf", "reason": "trailing_cagr_3y_far_above_5y", "note": "3y fcf growth (215%) is far above 5y growth (91%): the recent window captures a regime the 5y window does not. Do not treat either as a stable forward base." }, "implied_vs_5y_realized_pp": { "value": -59.92, "caveat": "5y_realized_base_unreliable" } } } } ``` Every success payload carries `freshness`, `as_of`, `market_status`, and `cache_age_seconds` (int; `0` when freshly computed) alongside the reverse-DCF body. ## How to read it [#how-to-read-it] **The headline is a requirement, not a prediction.** `implied.growth_required_pct.value` is `31.0` — the FCF CAGR the current enterprise value requires under the default assumptions. The `means` string restates it in words and ends with "not a forecast"; quote it that way: *"at today's price, NVDA is priced for roughly 31%/yr FCF growth for a decade."* Never write "StockContext projects 31% growth" — the tool projects nothing. **The anchor names the inputs.** `ttm_fcf_usd` is the trailing free cash flow the model grows; `enterprise_value_usd` is the price it inverts. `discounted_against: "enterprise_value"` means this is an FCFF-to-EV model (it sidesteps leverage, matching the EV multiples elsewhere in the product). The model is two-stage: stage-1 growth fades **linearly** to the 2.5% terminal rate over ten years (`growth_fade: "linear_to_terminal"`), then a Gordon terminal value caps it. The linear fade matters: `31.0` is the *year-one* rate that decays toward terminal, not a flat 31% held for a decade. **`all_fixed_defaults: true` is load-bearing.** Every assumption (10% discount, 10-year horizon, 2.5% terminal) is a fixed, clearly-labeled default — never a value fitted to NVDA. Do not present 10% as "StockContext's cost of capital for NVDA." It is a generic anchor; the grid exists precisely because the answer moves with it. ### The sensitivity grid: there is no single "true" number [#the-sensitivity-grid-there-is-no-single-true-number] `sensitivity.grid` is a 3×3 table: discount-rate rows (8 / 10 / 12%) by terminal-growth columns (2 / 2.5 / 3%). For NVDA the required growth ranges from **19.8% to 40.0%** across the corners. The model is most sensitive to the discount rate — a higher discount requires more growth to justify the same price, so the bottom row (12%) demands the most. Because the answer swings \~20 points across reasonable assumptions, never report the single headline cell as *the* implied figure: cite the headline and the range. If you only have room for one number, it is "\~31%, in a 20–40% band depending on the discount rate." ### Comparing implied to realized — the company's OWN history, not a benchmark [#comparing-implied-to-realized--the-companys-own-history-not-a-benchmark] `historical_realized_growth` is what NVDA **actually did**, per trailing window. This is the plausibility check, and it carries two traps the response defuses for you: * **It is not the benchmark the implied figure must beat.** Past growth rarely persists (mean reversion, the law of large numbers as the base gets huge). A realized FCF CAGR is context for judging whether the implied requirement is in the realm of what this business has produced — not a hurdle the requirement "passes" or "fails." * **`base_effect: "low_base_period"`** flags a CAGR inflated by a small starting value. For NVDA, **both** FCF windows trip it: the 3-year CAGR is `215.05` and the 5-year is `90.92`, each with a `base_note` saying the starting base was a few percent of today's free cash flow. So neither figure is a sustainable rate — both are base artifacts. When `base_effect` is `null` (NVDA's FCF windows are not), the response is explicitly asserting "no distortion," and you should prefer that un-flagged window. NVDA simply has no clean FCF window here. **`window_disagreement`** fires when the 3-year and 5-year CAGRs differ by more than 20 points — here `215%` vs `91%` for FCF, `reason: "trailing_cagr_3y_far_above_5y"`. That is the regime warning: the recent window captures an AI ramp the 5-year window does not, so neither is a stable forward base. When this object is present, do not extrapolate either CAGR; say the windows disagree and why. **`implied_vs_5y_realized_pp` is the signed gap, not a cheapness signal.** It is the difference between the implied requirement and the 5-year realized FCF CAGR. Read its **shape** before its sign: it is a bare float only when the 5-year base is clean; otherwise it is a `{ value, caveat }` object, and the caveat tells you the base is not trustworthy. For NVDA the 5-year window trips `low_base_period`, so the field ships as `{ "value": -59.92, "caveat": "5y_realized_base_unreliable" }`. The implied `31.0` is far *below* the unreliable `90.92` realized rate, hence the negative gap — but the caveat is the point: that `90.92` is a low-base artifact, so the gap is **not** a clean "implied below realized, therefore cheap" verdict. A negative `implied_vs_5y_realized_pp` is never a cheapness signal; pair it with the caveat and the `base_effect` flags before you read anything into it. ### Putting it together [#putting-it-together] > At today's price, NVDA is priced for **\~31%/yr FCF growth over a decade** (a 20–40% band across discount-rate assumptions). Its realized FCF CAGRs are higher — `91%` over five years and `215%` over three — but **both windows are flagged `low_base_period`**: the starting base was a few percent of today's free cash flow, so those rates are mechanically inflated and `window_disagreement` warns neither is a stable forward base. The signed gap (`implied_vs_5y_realized_pp`) ships as `{ value: -59.92, caveat: "5y_realized_base_unreliable" }` — the implied requirement sits below the realized rate, but the caveat says that realized rate is not a trustworthy base, so the gap is **not** a cheapness verdict. The honest frame: the price requires aggressive sustained growth, the company has no clean low-distortion growth window to judge it against, and `/v2/priced-in` says exactly that instead of pretending otherwise. This is a model of what the price requires, not a forecast or a target. That is the correct analytical frame. Note it draws a conclusion about the *price's requirement*, never about whether to buy. ## Refusals: when a reverse-DCF would mislead [#refusals-when-a-reverse-dcf-would-mislead] The tool refuses, rather than computing a misleading number, for issuers whose economics a FCF-on-EV model cannot represent. Each refusal is the standard `{ available: false, reason }` envelope with `shape_kind: "not_applicable"` (or the ETF `freshness: "unsupported"` shape) — a real answer, not an error to retry. A profitable company with positive trailing free cash flow computes normally: TSLA returns a full `reverse_dcf` with `growth_required_pct.value: 78.5`, not a refusal. | case | reason | what to say | | ------------------------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | Banks, insurers | `reverse_dcf_not_meaningful_financial_company` | financials have no meaningful FCF or EV; use `/v2/valuation` (it anchors on price/tangible-book for them) | | REITs | `reverse_dcf_not_meaningful_reit_use_ffo` | REITs need FFO/AFFO, not FCF; no such feed exists | | ETFs | `etf_no_reverse_dcf` | an ETF has no company free cash flow or enterprise value to invert | | Pre-profit (FCF ≤ 0 and earnings ≤ 0) | `non_positive_base_no_dcf_anchor` | the price can't solve for growth without first assuming when the base turns positive — the tool won't fake that | | Too little history | `insufficient_ttm_history` | fewer than four quarters of cash-flow statements | There is also no earnings-basis fallback in this version: one basis per response, always trailing free cash flow (`basis: "fcf_ttm"`). ### Solver boundaries are not verdicts [#solver-boundaries-are-not-verdicts] When the price lies outside the model's representable growth range, `growth_required_pct` is replaced by a neutral boundary envelope: `price_below_solver_floor` (even a deep decline overshoots the price) or `price_above_solver_range` (even the model's growth ceiling can't reach the price), both carrying the fixed `detail`: "model-range boundary, not a cheap/expensive judgment." Read these as *the price is outside what the model spans*, never as cheap or expensive. The same applies to a `null` cell in the grid — that assumption pair put the price outside the bracket. Pair priced-in with the own-history percentile read for the full valuation picture. # SEC risk review (/docs/guides/sec-risk-review) Reading a 10-K risk section is a three-call walk: list filings, pick the accession, fetch one section. You never download the whole filing, and you summarize from the text the section endpoint returns: nothing from news, memory, or consensus gets blended in. ## The walk [#the-walk] List periodic filings and choose the form you actually want. Read the section index on the chosen accession to see which sections exist and how large they are. Fetch one section by name and summarize from that text. ## 1. List filings, pick the accession [#1-list-filings-pick-the-accession] `/v2/filings` defaults to `form=10-K,10-Q,8-K` and `limit=20`. Pass a tighter `form` CSV when you only want the annual report. Here, `form=10-K` returns just the 10-K rows for AAPL. ```python title="list_filings.py" import httpx r = httpx.get( "https://api.stockcontext.com/v2/filings", params={"symbol": "AAPL", "form": "10-K", "limit": 5}, headers={"X-API-Key": "sctx_..."}, ) filings = r.json()["data"]["filings"] latest = filings[0] # rows come back newest-first accession = latest["accession"] ``` ```typescript title="listFilings.ts" const res = await fetch( "https://api.stockcontext.com/v2/filings?symbol=AAPL&form=10-K&limit=5", { headers: { "X-API-Key": "sctx_..." } }, ); const { data } = await res.json(); const latest = data.filings[0]; // rows come back newest-first const accession = latest.accession; ``` The latest 10-K row, copied from the live response (the full list also carries 10-Q and 8-K rows when you widen `form`): ```json title="filings.data.filings[…] (one row)" { "accession": "0000320193-25-000079", "form": "10-K", "filing_date": "2025-10-31", "period_of_report": "2025-09-27", "items": [], "items_text": [], "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019325000079/aapl-20250927.htm", "size_bytes": 9392337, "sections": [] } ``` That `size_bytes` is 9.4 MB. You are not going to read it whole; `sections` is empty on the list because the index lives on the next call. If the user asks for "the latest periodic filing" rather than the 10-K specifically, request `form=10-K,10-Q`, then pick by `filing_date` and tell the reader which form you used. Calling something "the latest 10-K" after selecting a 10-Q is the most common reporting error here. ## 2. Read the section index [#2-read-the-section-index] `GET /v2/filings/0000320193-25-000079` returns the section index with a `char_count` per section. That count is your budget: it tells you how much text the next call will return before you spend it. ```json title="filing-10k.data (trimmed)" { "symbol": "AAPL", "symbol_status": { "resolved": true }, "accession": "0000320193-25-000079", "form": "10-K", "filing_date": "2025-10-31", "period_of_report": "2025-09-27", "sections": [ { "name": "business", "char_count": 16004 }, { "name": "risk_factors", "char_count": 68069 }, { "name": "mda", "char_count": 21009 } ] } ``` Section names are per-form, so check `sections[].name` before requesting one. A 10-K carries `business`, `risk_factors`, `mda`. A 10-Q carries `mda`, `risk_factors`, `quantitative_qualitative_disclosures`, `controls_and_procedures`; note it has no `business` section. Don't assume a name exists on one form because it appeared on another. 8-K metadata has a different shape: no `sections` array at all. Instead it carries `items` (the disclosed item codes with titles, e.g. `2.02: Results of Operations and Financial Condition`) and a `press_release` object with `available` and `char_count`. When `press_release.available` is true, fetch it with `/section/press_release`; the `risk_factors`-style names do not exist on an 8-K. `items` and `items_text` on list rows are 8-K item codes too, empty arrays on 10-K/10-Q rows. Form 4 metadata carries neither sections nor items, just the filing facts; its `period_of_report` is usually populated but can be null. The accession routes accept an optional `?symbol=` cross-check. `GET /v2/filings/0000320193-25-000079?symbol=AAPL` confirms the accession belongs to AAPL; a mismatch returns `PARAM_INVALID`. A well-formed accession that matches no filing returns 400 `PARAM_INVALID` (`Accession '…' was not found on SEC EDGAR…`), and a malformed one is also 400 with a distinct `is malformed` message; neither is a 200. The `symbol_status` `{ "resolved": false, "reason": "symbol_unresolved_from_accession" }` shape appears only for a *real* filing whose filer doesn't map to a supported ticker, not for a missing accession. See [error codes](/docs/reference/error-codes) for the full set. ## 3. Fetch one section [#3-fetch-one-section] `GET /v2/filings/0000320193-25-000079/section/risk_factors` returns the text and echoes the same `char_count`. Fetch only the section you intend to read. ```json title="filing-section-risk-factors.data (text trimmed)" { "symbol": "AAPL", "accession": "0000320193-25-000079", "form": "10-K", "section": "risk_factors", "char_count": 68069, "text": "Item 1A. Risk Factors\nThe following summarizes factors that could have a material adverse effect on the Company's business... Macroeconomic and Industry Risks... Beginning in the second quarter of 2025, new tariffs were announced on imports to the U.S. ...", "freshness": "intraday", "market_status": "open", "cache_age_seconds": 12, "available": true } ``` The `text` above is trimmed for the page; the live response returns the full 68,069 characters. Summarize from that text and nothing else. A risk summary built only from this section: ```text title="answer" Top risks in Apple's FY2025 10-K (filed 2025-10-31, risk_factors) - Tariffs: new U.S. tariffs from Q2 2025 on China, India, Japan, South Korea, Taiwan, Vietnam, and the EU, plus a Section 232 semiconductor investigation; impact described as uncertain. - Supply concentration: manufacturing is heavily outsourced to a small set of Asian partners, with single-source components. - Antitrust and the DOJ/Google search case, where remedies on appeal could materially affect the search licensing revenue Apple earns from Google. Source: accession 0000320193-25-000079, section risk_factors, as_of 2026-06-05T14:19:24Z, freshness intraday. ``` Every bullet traces to a sentence in the returned `text`. That is the guardrail: a SEC risk summary is a summary of one section's words. Do not add a headline, an analyst note, a price reaction, or a remembered fact: if it is not in the section text, it does not go in the answer. This is the anti-hallucination rule from the [agent instructions](/docs/agents/instructions), applied to filings. ## MCP shape [#mcp-shape] The hosted tools take the same walk, with one argument difference: REST `form` is a CSV string, but the MCP `forms` argument is a JSON array. ```json stockcontext_filings_list({ "symbol": "AAPL", "forms": ["10-K"], "limit": 5 }) stockcontext_filings_get({ "accession": "0000320193-25-000079" }) stockcontext_filings_section({ "accession": "0000320193-25-000079", "name": "risk_factors" }) ``` ## Cold first call [#cold-first-call] The first read of an accession pulls from EDGAR and can take up to \~20 seconds for a large 10-K; repeat reads of the same accession return in well under a second. Listing filings is fast either way; the cold cost is per accession. Don't block a whole UI on it. See [coverage and gaps](/docs/reference/coverage-and-gaps) for the latency note. Filing and insider data are also `unsupported` for the 1,125 foreign private issuers that file 20-F/40-F/6-K, and for ETFs; the row will carry a machine-readable reason rather than text. Form 4 activity is a separate endpoint with its own rollup, not a filing section. # Technical timing check (/docs/guides/technical-timing) "Is AAPL extended?" is a two-call question: `/v2/technicals` gives the indicator read, `/v2/price-action` gives the range-and-volume context. Both are precomputed, so you do not pull `/v2/history` to answer it; history is for when the user wants the actual rows. ## Two calls [#two-calls] ```python title="timing.py" import httpx h = {"X-API-Key": "sctx_..."} tech = httpx.get("https://api.stockcontext.com/v2/technicals", params={"symbol": "AAPL"}, headers=h).json()["data"] pa = httpx.get("https://api.stockcontext.com/v2/price-action", params={"symbol": "AAPL"}, headers=h).json()["data"] ``` ```typescript title="timing.ts" const h = { "X-API-Key": "sctx_..." }; const tech = (await (await fetch( "https://api.stockcontext.com/v2/technicals?symbol=AAPL", { headers: h })).json()).data; const pa = (await (await fetch( "https://api.stockcontext.com/v2/price-action?symbol=AAPL", { headers: h })).json()).data; ``` ## The technical read [#the-technical-read] Trimmed from a live AAPL response, the fields that answer "extended?": ```json title="technicals.data (trimmed)" { "freshness": "end_of_day", "market_status": "closed", "as_of": "2026-06-08T00:55:14Z", "cache_age_seconds": 378, "moving_averages": { "sma_50": 281.08, "sma_200": 264.76, "vs_sma_50_pct": 9.34, "vs_sma_200_pct": 16.08, "golden_cross_active": true, "distance_from_52w_high_pct": -3.03 }, "momentum": { "rsi_14": 60.82, "rsi_zone": "neutral", "rsi_14_weekly": 68.57, "rsi_14_weekly_zone": "neutral", "stochastic_14_3_3": { "k": 66.73, "d": 74.73, "zone": "neutral" }, "macd": { "value": 8.51, "signal_line": 9.45, "histogram": -0.94, "above_signal": false } }, "trend_strength": { "adx_14": 45.51, "trending": true, "obv_trend_30d": "up" }, "volatility": { "bollinger_20": { "percent_b": 0.61 }, "beta_1y_weekly": 1.1 } } ``` How to read each piece: * **`rsi_zone`** is the daily RSI bucket. Here daily `rsi_14` is 60.82, zone `neutral`; the weekly `rsi_14_weekly` is 68.57, also `neutral` but pressing toward overbought. The zone enum you will see across symbols: `oversold`, `neutral`, `overbought`. Daily and weekly can disagree, so report both rather than collapsing them. * **`golden_cross_active: true`** means the 50-day SMA sits above the 200-day. Combined with `adx_14` of 45.51 and `trending: true`, the trend is strong and intact. * **`distance_from_52w_high_pct: -3.03`** puts price 3.03% under the 52-week high, near the top of its range. * **`percent_b: 0.61`** places price at 61% of the way up the 20-day Bollinger band: above the midline, not pinned to the upper band. ## The price-action context [#the-price-action-context] ```json title="price-action.data (trimmed)" { "freshness": "end_of_day", "market_status": "closed", "as_of": "2026-06-05T20:00:00Z", "cache_age_seconds": 2557, "volume": { "latest": 65310502, "avg_30d": 49210144, "ratio_to_avg": 1.33 }, "recent_extremes": { "high_90d": { "value": 316.94, "date": "2026-06-03" }, "low_90d": { "value": 245.28, "date": "2026-03-30" } }, "streaks": { "direction": "none", "consecutive_days": 0, "biggest_down_day_1y_pct": -5.0 }, "drawdowns": { "max_90d": { "pct": -5.44 }, "max_1y": { "pct": -13.8 } } } ``` `ratio_to_avg` of 1.33 says the latest session traded about a third above the 30-day average volume: the move up has buying behind it, not a thin tape. The 90-day drawdown of -5.44% (keep the sign) shows the recent pullback was shallow. No active streak. ## The answer they support [#the-answer-they-support] ```text title="answer (3 lines)" AAPL is firmly in an uptrend but not stretched. Daily and weekly RSI are both neutral (60.8 and 68.6, the weekly pressing toward overbought); price sits 3.0% below its 52-week high at Bollinger %B 0.61. The trend is strong and intact (golden cross, ADX 45.5) on above-average volume (1.33x the 30-day). ``` Every clause traces to a field above. That is the whole job: read the precomputed indicators, weigh daily against weekly, keep the signs, and stop. `market_status` (`open` during the session, `closed` otherwise) is a session flag for whether regular trading hours are in effect. It is not an execution signal or an exchange feed; do not imply order timing from it. See [freshness](/docs/reference/freshness) for `market_status`, `as_of`, and the `degraded` state newer listings can return. ## When you actually need the series [#when-you-actually-need-the-series] If the question needs rows (to draw a chart, or feed a model), call [`/v2/history`](/docs/api-reference/endpoints/market-context/history): `series` is a CSV of up to 3 (e.g. `close,rsi_14`), `range` defaults to `1y`. Don't reach for it to answer "extended?"; technicals and price-action already hold the indicator, range, volume, streak, and drawdown context. To put these technical fields side by side across 2-12 symbols, use compare. # Valuation check (/docs/guides/valuation-check) Call `/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 [#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: ```json "pe_ttm": { "value": 31.23, "vs_own_history": { "percentile": { "3y": 1, "5y": 0, "10y": 8 }, "median_5y": 57.66, "label": "near_5y_low", "n": 1238 }, "change": { "1y": { "price_pct": 45.91, "fundamental_pct": 109.97, "driver": "fundamentals_outran_price" }, "3y": { "price_pct": 422.01, "fundamental_pct": 3373.4, "driver": "fundamentals_outran_price" } } } ``` Read it in three parts: * **`value`** is 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_history`** ranks that level inside the symbol's own past. `percentile` is 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_5y` is the magnitude anchor (57.66x — the multiple has usually been far higher). `label` bands the 5-year percentile into one closed reading; `n` is the clean sample size behind it (1238 daily points here). * **`change`** is the decomposition that makes the rank legible: how much the **price leg** moved versus the **fundamental leg** over 1 year and 3 years, and a `driver` naming which leg moved the multiple. ### The label vocabulary — and what `near_5y_low` does NOT mean [#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 | When only three years of clean history qualify, the labels carry a `_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-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? [#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. The `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 | In the NVDA example the 1-year driver is `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? [#history_regime-is-the-percentile-comparing-todays-company-to-one-that-no-longer-exists] A top-level `history_regime` block guards the percentile axis itself: ```json "history_regime": { "window": "5y", "regime_break": true, "drivers": [ { "metric": "revenue", "change_x": 11.6, "direction": "growth" }, { "metric": "eps", "change_x": 38.4, "direction": "growth" } ] } ``` `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? [#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 returns `ps_ttm`, `pb`, the EV multiples, a `yields` block, and `returns_on_capital`): ```json title="GET /v2/valuation?symbol=NVDA (trimmed)" { "data": { "symbol": "NVDA", "as_of": "2026-06-08T13:07:46Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 0, "market_cap_usd": 4983930000000, "enterprise_value_usd": 4915706000000, "multiples": { "pe_ttm": { "value": 31.23, "vs_own_history": { "percentile": { "3y": 1, "5y": 0, "10y": 8 }, "median_5y": 57.66, "label": "near_5y_low", "n": 1238 }, "change": { "1y": { "price_pct": 45.91, "fundamental_pct": 109.97, "driver": "fundamentals_outran_price" }, "3y": { "price_pct": 422.01, "fundamental_pct": 3373.4, "driver": "fundamentals_outran_price" } } } }, "history_regime": { "window": "5y", "regime_break": true, "drivers": [ { "metric": "revenue", "change_x": 11.6, "direction": "growth" }, { "metric": "eps", "change_x": 38.4, "direction": "growth" } ] }, "valuation_context": { "anchor": "pe_ttm", "rationale": "positive_earnings_period", "value": 31.23, "label": "near_5y_low" }, "fundamentals_trajectory": { "revenue_growth": { "latest_yoy_pct": 70.68, "trend": "stable" }, "eps_growth": { "latest_yoy_pct": 109.97, "trend": "accelerating" }, "gross_margin": { "latest_pct": 74.93, "trend": "increasing" } } } } ``` The MCP equivalent is `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 (`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 the `driver` is `fundamentals_outran_price` — the multiple compressed because earnings outran the stock, not because the stock fell. `history_regime.regime_break` is `true` (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. On `pe_ttm` the windows agree (5-year 0, 10-year 8) so no `window_divergence` fires; it does fire on `ps_ttm`, where the 3-year (4) and 10-year (47) percentiles tell different stories. Growth is still strong (revenue +70.68% YoY `stable`, 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. 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. ## Non-payers: the dividend field is absent, not zero [#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 its `yields` block reports: ```json title="GET /v2/valuation?symbol=TSLA (yields, illustrative)" "yields": { "fcf_yield_pct": 0.45, "earnings_yield_pct": 0.25, "dividend_yield_pct": { "available": false, "reason": "no_dividend_payer" }, "buyback_yield_pct_ttm": 0.0, "total_payout_yield_pct_ttm": 0.0 } ``` Read `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-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](/docs/reference/freshness) covers the `unsupported` state and how to branch on it. ## Going deeper: what is the price priced for? [#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](/docs/guides/reverse-dcf). Screen 2–12 names first, then run this check on the finalists only. # Authentication (/docs/reference/authentication) Send your key on every REST request in the `X-API-Key` header. ```bash curl "https://api.stockcontext.com/v2/snapshot?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` Use exactly that header name. There is no `Authorization: Bearer` path for REST, and the hosted MCP server authenticates with the same `X-API-Key` header. ## Keys [#keys] Keys start with the `sctx_` prefix. Create and revoke them at [the dashboard](https://stockcontext.com/dashboard/keys). * The raw secret is shown **once**, at creation. Copy it before you close the dialog. StockContext cannot show it again. * Keys do not expire on their own. They stay valid until you revoke them. * Revocation takes effect within about a minute. After that, the key returns `UNAUTHORIZED`. Keep keys server-side. Do not ship them in browser bundles, mobile binaries without a backend broker, public repos, logs, screenshots, support threads, or model prompts. A leaked `sctx_` secret can call your whole quota until you revoke it. ## When a key fails [#when-a-key-fails] A missing, malformed, or revoked key returns HTTP `401` with `UNAUTHORIZED`: ```json { "error": { "code": "UNAUTHORIZED", "message": "Missing or invalid X-API-Key header.", "retryable": false } } ``` This is not retryable. Retrying with the same key wastes requests against your [rate limit](/docs/reference/plans-and-limits). Fix the header, swap in a live key, or create a new one. For the full status-and-retry table, see [error codes](/docs/reference/error-codes). # Changelog (/docs/reference/changelog) ## 2026-06-17: annual-only foreign private issuers never serve a bare-empty quarter view [#2026-06-17-annual-only-foreign-private-issuers-never-serve-a-bare-empty-quarter-view] Correctness release across `/v2/fundamentals` and `/v2/snapshot`; two response shapes gain a value-or-envelope variant and a new field: * **New top-level `reporting_cadence` on `/v2/fundamentals`** (`'quarterly'` | `'annual'`). It labels the issuer's statement filing cadence. The universe default is `'quarterly'` — domestic issuers and foreign filers that report quarterly (e.g. SHOP, SHEL) are unchanged. An annual-only foreign private issuer (20-F / IFRS filer with no quarterly statements — e.g. TSM, SAP) reports `'annual'`. Branch on this before assuming quarterly rows exist. * **The DEFAULT view (no `?period=`) now resolves to the rich annual statements for an annual-only filer.** Previously these filers returned a bare-empty quarterly view. The default now serves the native-currency ANNUAL income statement, balance sheet, and cash flow, the `ttm` block is computed off the latest fiscal year (reason `ttm_basis_latest_fiscal_year_annual_filer`, never the misleading `insufficient_quarterly_history`), and `forensics` returns `{available: false, reason: "annual_only_filer_no_quarterly_forensics"}`. Request `?period=annual` for the same statements explicitly. * **An explicit `?period=quarter` for an annual-only filer returns a `quarterly_view` notice instead of empty rows.** The response carries `quarterly_view: {available: false, reason: "annual_only_filer_use_period_annual"}`, the quarterly statement blocks are OMITTED (not bare-empty), and the `ttm` block is still served (latest fiscal year). `quarterly_view` is absent for every quarterly filer. * **`/v2/snapshot` `fundamentals_quick` gains an optional `reporting_cadence: 'annual'`** present ONLY for an annual-only filer, whose `ttm_*` figures there are taken from the latest fiscal year. Absent for the quarterly universe. These filers are no longer marked `degraded` for lacking quarterly history. * **Cross-currency refusals are unchanged.** For a non-USD foreign private issuer the `ttm_*_usd` magnitudes still refuse `value_in_native_currency_see_fundamentals`; the annual cadence change serves correctly-labeled native statements, never native magnitudes under a USD label. * **`schema_version`** bumped to `2026-06-17.6` to mark the new `reporting_cadence` field and `quarterly_view` value-or-envelope variant. Starter and Builder plans. ## 2026-06-17: leaner per-row provenance (derivable filing URL dropped) [#2026-06-17-leaner-per-row-provenance-derivable-filing-url-dropped] Payload-size release (`/v2/fundamentals`): * **Per-row provenance no longer ships `filing_url`.** Each income-statement, balance-sheet, and cash-flow row's `provenance` block now carries `{accession, filed, form}` only. The archive index URL is fully derivable from the accession — its leading 10-digit block IS the filer CIK — as `https://www.sec.gov/Archives/edgar/data/{cik}/{accession-without-dashes}/{accession}-index.htm` (taught in the `accession` field description). This matches the `/v2/facts?provenance=full` per-cell shape (which already omitted the URL) and removes a \~80-character derivable string that was triplicated across the three statement rows of every period. Provenance integrity is unchanged: every row is still independently citable by accession, and the vendor / unresolved value-or-envelope refusals are unchanged. * **`schema_version`** bumped to `2026-06-17.5` to mark the field removal. Starter and Builder plans. ## 2026-06-17: foreign-flip cross-surface currency, sign, and cash-flow safety [#2026-06-17-foreign-flip-cross-surface-currency-sign-and-cash-flow-safety] Correctness release across the price-derived surfaces, ahead of foreign issuers flipping to the SEC-native (native-currency) store: * **Cross-currency multiples and enterprise value refuse instead of mixing currencies.** For a foreign private issuer reporting in a non-USD functional currency (e.g. EUR for SAP, TWD for TSM), a multiple whose denominator is a native-currency statement value — `pe_ttm`, `ps_ttm`, `pb`, `ev_ebitda_ttm`, `ev_ebit_ttm`, `ev_sales_ttm` — and `enterprise_value_usd` (a USD market cap minus native net debt) now return `{available: false, reason: "cross_currency_multiple_unavailable"}` on `/v2/snapshot` and `/v2/valuation` rather than dividing a USD-quote market cap by a native denominator. The reciprocal yields (`earnings_yield_pct`, `fcf_yield_pct`) refuse the same way. **Market cap itself still serves** — `price (USD) × shares` is genuinely USD — as do currency-invariant ratios (dividend/buyback/total-payout yields, returns on capital, growth %). The correctly-labeled native statements live on `/v2/fundamentals` and `/v2/facts`. * **Native magnitudes are never served under a `_usd` label.** The TTM `_usd`-suffixed figures on `/v2/snapshot` (`ttm_revenue_usd`, `ttm_net_income_usd`, `ttm_eps_diluted`) and `/v2/brief` (`revenue_ttm_usd`, `net_income_ttm_usd`, `total_debt_usd`, `free_cash_flow_usd`, …), and `/v2/earnings` `eps_actual`/`revenue_actual`, return `{available: false, reason: "value_in_native_currency_see_fundamentals"}` for a flipped foreign issuer. `/v2/brief` `identity.currency` reports the statements' native currency. * **`/v2/earnings` `currency` widens from `Literal["USD"]` to a free string** — the issuer's native functional currency for SEC-native foreign filers, still `"USD"` otherwise. * **`/v2/priced-in` refuses the reverse-DCF for a cross-currency issuer** with `{available: false, reason: "reverse_dcf_cross_currency_unavailable"}` (a USD market cap cannot anchor a native-currency cash-flow path), rather than implying a fair value off mixed currencies. * **The shared TTM aggregator now applies the impossible-sign refusal.** An impossible-negative revenue/COGS/opex in any contributing quarter refuses the TTM magnitude (and therefore `ps_ttm`, `ev_sales_ttm`, `revenue_ttm_usd`, and the brief) — previously the per-row guard caught the presentation layer but the canonical TTM sum bypassed it. Sign-meaningful fields (net income, FCF, equity) are unaffected. * **Cash-flow articulation is now enforced surface-wide.** A period whose `operating + investing + financing + FX-on-cash` fails to reconcile to the filed net change in cash refuses `fcf_yield_pct` (`{available: false, reason: "cf_articulation_unverified"}`) on `/v2/snapshot` and `/v2/valuation`, refuses the `/v2/priced-in` reverse-DCF, and nulls the operating/free cash-flow cells on the `/v2/facts` grid (registered in the block `unavailable` registry) — matching the guard already on `/v2/fundamentals`. * **`schema_version`** bumped to `2026-06-17.4` to mark these new value-or-envelope variants and the earnings `currency` widening. Starter and Builder plans. ## 2026-06-17: no-weirdness gaps — supported-symbol earnings + impossible-sign refusal [#2026-06-17-no-weirdness-gaps--supported-symbol-earnings--impossible-sign-refusal] Data-quality release; two response shapes gain a value-or-envelope variant: * **`/v2/earnings` never walls a supported symbol.** A supported issuer whose SEC filings carry no discrete fiscal quarters (foreign private issuers filing annual 20-F / semiannual 6-K, and early SPACs — e.g. RIO, SHEL, KB) previously returned a retryable `503 UPSTREAM_UNAVAILABLE`. It now returns `200` with `last` as a value-or-envelope (`{available: false, reason: "no_quarterly_statements_filed"}`) — the symbol is supported, there is simply no quarterly row to report. A true statements outage (no data at all) still returns `503`. `/v2/coverage` no longer advertises `earnings` as quote-blocked: earnings reads only filed statements (never a live quote). * **`/v2/fundamentals` refuses impossible-sign magnitudes.** `income_statement.revenue`, `cost_of_revenue`, and `operating_expense` are magnitudes that cannot be negative. A served negative (an EDGAR de-cumulation FY−9M derivation artifact, or a vendor sign error) is now refused as `{available: false, reason}` — `decumulation_sign_implausible` for a derived quarter, `reported_value_implausible` otherwise — instead of being served bare. Sign-meaningful fields (`net_income`, `operating_income`, free cash flow, equity) can legitimately be negative and are unchanged. * **`/v2/facts` mirrors the sign-sanity refusal.** An impossible-negative magnitude cell is nulled and explained in the grid's `unavailable` registry with the same reason tokens. * **`schema_version`** bumped to `2026-06-17.3`. Starter and Builder plans (`/v2/coverage` is free-tier). ## 2026-06-17: one-call analyst brief (the front door) [#2026-06-17-one-call-analyst-brief-the-front-door] Additive release (one new route); no existing response shape changes: * **New `GET /v2/brief?symbol=`.** One compact response (\~6-8KB) that composes the headline of every analyst surface, so an agent can start reasoning from a single call instead of fanning out across seven. Sections: `identity` (name, exchange, sector/SIC, CIK); `price` (quote, seven-window performance, 52-week range); `ttm` (revenue / net income / EPS, gross / operating / net margins, cash, total\_debt, net\_debt, total\_equity, operating and free cash flow); `valuation` (market cap, EV, and P/E and EV/EBITDA each with their own-history band, z-score, and price-vs-fundamental driver); `capital_allocation` (dividend yield + payer flag, FCF yield, TTM buybacks and dividends paid); `events` (the latest three classified SEC events); `insider_90d` (Form 4 open-market net); `segment` (top business segment by revenue); and `next_earnings` (projected from filing cadence, never issuer-confirmed). * **Pure composition — no new data, no extra freshness surface.** The brief reads the same warm caches the dedicated endpoints read; the numbers match them exactly (same assemblers). It performs zero EDGAR or vendor-fundamentals fetches on the request path. * **Every section is value-or-envelope and the route never fails for a supported symbol.** An unavailable input returns `{available: false, reason}` (e.g. `segments_store_not_built`, `not_applicable_for_etf`, `price_quote_unavailable`) — never a bare null, and one missing section never turns into a route-level error. The response header carries `statements_source` (`sec_edgar` | `vendor`) and a `coverage_hint` (`sec_native` | `vendor_fallback` | `statements_unavailable` | `etf_market_data_only`) so a thin payload still says where its numbers came from. ETFs envelope the stock-only sections. * **MCP:** new tool `stockcontext_brief` — the recommended first call for any single symbol. * **`schema_version`** bumped to `2026-06-17.2` to mark the new route. Starter and Builder plans. ## 2026-06-17: reference catalogs for cold discovery [#2026-06-17-reference-catalogs-for-cold-discovery] Additive release (three new free-tier routes); no existing response shape changes: * **New `GET /v2/reference/fact-concepts`.** Lists every concept name accepted by `/v2/facts?concepts=` — each entry carries the stable public `name`, its `unit`, a human `label`, and the `aliases` (the SEC XBRL tags and common GAAP-longhand spellings) that resolve to it. Fetch this once to discover the vocabulary instead of guessing a name and getting `not_in_catalog`. * **New `GET /v2/reference/event-labels`.** Lists the full classified-event label taxonomy `/v2/events` emits — each entry gives the machine `label`, its SEC classification `source` (`8k_item`, `form`, or `xbrl`), and the exact item codes / form types behind it (the valid `labels=` filter values). * **New `GET /v2/reference/segment-axes`.** Lists the segment dimensions `/v2/segments` populates (business, geography) with their SEC XBRL axes and the revenue/profit concepts read per member. * **`/v2/facts` resolves common GAAP-longhand concept names.** The obvious snake\_case guess now resolves to the cataloged concept (`research_and_development_expense` -> `rnd_expense`, `net_income_loss` -> `net_income`, `cost_of_goods_sold` -> `cost_of_revenue`, and more) and serves real data, in addition to the existing exact-name, case-insensitive, and XBRL-tag resolution. * **`/v2/facts` never returns a bare `not_in_catalog`.** An unresolved concept that shares a word with a real concept now always carries `did_you_mean` suggestions (typos, partial names, and longhand guesses), so a miss points somewhere instead of dead-ending. * **MCP:** new resources `stockcontext://reference/fact-concepts`, `stockcontext://reference/event-labels`, and `stockcontext://reference/segment-axes` mirror the routes for in-tool discovery. * **`schema_version`** bumped to `2026-06-17.1` to mark the new routes. All three are free-tier accessible like `/v2/coverage`. ## 2026-06-16: foreign-issuer native currency and per-row SEC provenance [#2026-06-16-foreign-issuer-native-currency-and-per-row-sec-provenance] Additive SEC-data release (`/v2/fundamentals`): * **Foreign private issuers now serve their native reporting currency.** `currency` widens from a fixed `"USD"` to the filer's own functional currency for SEC-native foreign private issuers reporting under IFRS (e.g. `"EUR"` for SAP, `"TWD"` for TSM), matching the currency already served on `/v2/facts` for the same symbol. Domestic issuers and the vendor fallback remain `"USD"`. Every monetary field in the response is denominated in this currency. * **Every statement row carries its SEC filing citation.** Income-statement, balance-sheet, and cash-flow rows served from the EDGAR-native store gain a `provenance` block: the period's primary filing as `{accession, filing_url, filed, form}` (a bare value, no `available: true` wrapper — value-or-envelope), where `filing_url` is a resolvable `https://www.sec.gov/Archives/...` index URL built from the filer CIK and accession (the same shape `/v2/facts?provenance=full` exposes per cell). Vendor-sourced rows carry `{available: false, reason: "vendor_sourced_no_sec_provenance"}` (the price vendor has no SEC filing to cite); the rare EDGAR row whose filing identity is unrecoverable carries `{available: false, reason: "sec_filing_url_unresolved"}`. Never a bare null. * **Foreign issuers with coherent IFRS facts now serve `sec_edgar`.** The SEC coherence flip referee no longer carves out foreign issuers wholesale: a foreign filer whose `ifrs-full` facts pass the full hard-gate identity battery (balance-sheet `A = L + E`, gross profit, EPS, facts-vs-fundamentals TTM) flips to SEC-native serving exactly like a domestic issuer, now that native currency and per-row provenance make the payload citable. Foreign issuers whose facts do not pass remain on the vendor fallback. (IFRS `total_equity` is pinned to the including-NCI total with parent-only book value served as `total_equity_parent`, and net income is served on the owners-of-parent basis so EPS reconciles — the same conventions domestic issuers already follow.) * **`schema_version`** bumped to `2026-06-16.2` to mark the payload-shape change. * **Token budgets:** `fundamentals` raised 7,195 -> 9,367 because the published example now carries a per-row `provenance` block on all three statement blocks (G-BUDGET +10% headroom rule). ## 2026-06-16: quote-miss safety and honest coverage [#2026-06-16-quote-miss-safety-and-honest-coverage] Reliability/contract correction: * **`/v2/snapshot` now degrades when a stock's live quote is unavailable.** The route keeps serving the rest of the snapshot and returns `quote: {available: false, reason: "price_quote_unavailable"}` instead of turning a quote miss into a route-level 404/503. * **`/v2/valuation` and `/v2/priced-in` no longer value from fallback closes after a live quote miss.** Market cap and EV fields that require the quote basis return unavailable envelopes with `share_price_basis_unverified`; `/v2/priced-in` refuses the reverse-DCF as `shape_kind: "not_applicable"` with the same reason rather than silently computing from historical bars. * **Vendor fundamentals/history walls now degrade instead of sinking M0 routes.** Snapshot, valuation, priced-in, and fundamentals read the requested symbol's own warm SEC stores when available; if neither vendor nor store data is available, they return degraded payloads with unavailable fields instead of a route-level provider error. No endpoint relabels a sibling share class's vendor bars or statements. * **`/v2/coverage` is more honest for known unquoted share classes.** Routes that still require a live quote now report `available_now: false` with `share_class_quote_unmapped`; M0 routes that can degrade remain available. * **`/v2/fundamentals` no longer serves a consolidated net income beside per-class EPS/shares.** For multi-class issuers (GOOGL/BRK class), a quarter that carries the quoted class's own per-class `eps_diluted`/`shares_diluted` but no reconciling per-class net-income allocation now withholds `net_income` as `{available: false, reason: "class_basis_net_income_unavailable"}` instead of leaving the consolidated total (which broke `eps_diluted x shares_diluted = net_income` by \~13% on GOOGL Q1-2026). This is the same guard `/v2/facts` already applied, now ported to the statements store; clean single-class symbols and reconciling annual periods are unchanged. * **`/v2/fundamentals` cash-flow section totals are now value-or-envelope when a period does not articulate.** When a period's `operating_cash_flow + investing_cash_flow + financing_cash_flow + FX-on-cash` fails to reconcile to the filed net change in cash (typically an untagged FX line modern filers fold into the cash total), those three section totals — and the operating-cash-flow-derived `free_cash_flow`, `fcf_margin_pct`, `ocf_yoy_pct`, `fcf_yoy_pct` — return `{available: false, reason: "cf_articulation_unverified"}` instead of an internally inconsistent set of totals. This pairs with a change in the SEC coherence flip referee that no longer benches an entire dollar-correct symbol (income statement and balance sheet still serve from SEC) over a single cash-flow articulation miss, so more symbols serve SEC-native statements while a non-articulating cash flow is honestly refused. * **`schema_version`** bumped to `2026-06-16.1` to mark the payload-shape change. ## 2026-06-15: public analyst core positioning [#2026-06-15-public-analyst-core-positioning] Product-surface change, no REST route deletion: * **Public core:** docs, MCP, endpoint chooser, and agent recipes now position StockContext as a bounded analyst-grade core for active US-listed stocks and ETFs: search, coverage, snapshot, profile, price history, technicals, price action, compare, fundamentals, facts, valuation, priced-in growth, earnings actuals and company-stated guidance, dividends, filings, filing sections/diffs, insider Form 4/Form 144, SEC-derived events, and calendar. * **Private beta:** segments, ownership/13F/13D-G, and governance remain REST-callable for explicitly enabled integrations, but are removed from public MCP tool discovery and default public docs navigation until their coverage gates are strong enough. * **Coverage contract:** `/v2/coverage` now carries `public_visibility` so agents can distinguish `public_core`, `private_beta`, and `internal_only` from `quality_grade` and `available_now`. * **Coverage source labels:** public coverage contract output uses vendor-neutral source labels such as `market_data`, `market_metadata`, and `price_history`; internal contracts can stay provider-specific. * **MCP:** public tool discovery now exposes 21 tools instead of 24. * **Token budgets:** `coverage` raised 464 -> 3,732 and `coverage-etf` raised 670 -> 3,539 because the published coverage examples now include the full production contract fields plus `public_visibility`, and live coverage can carry larger availability state. Live G-BUDGET now gates public-core routes by default; private-beta route budgets remain available with an explicit opt-in check. ## 2026-06-14: segmented financials and coverage data-state [#2026-06-14-segmented-financials-and-coverage-data-state] Additive SEC-data release: * **New endpoint:** `GET /v2/segments?symbol=AAPL` serves the precomputed multi-period SEC XBRL segment grid (business/geographic members, revenue, operating profit, accessions, filed dates, recast flags, and G-SEG reconciliation) on Starter and Builder plans. The route is cache-only: no request-path EDGAR fetch. A missing bootstrap returns `segments: {available: false, reason: "segments_store_not_built"}`; a built store with no usable breakout returns `segment_data_not_disclosed`. * **Coverage honesty:** `/v2/coverage` now includes the `segments` family and documents data-state fields for store-backed SEC families. Branch on `support[family].available_now`; `supported` remains route-level/back-compat support. * **MCP:** new tool `stockcontext_segments` was added at release time; see 2026-06-15 for the later public-core/private-beta split. * **`schema_version`** bumped to `2026-06-14.4` to mark the segmented-financials and coverage data-state shape. * **Token budgets:** `segments` budget added at 497 tokens. Raised `coverage` 161 -> 464 and `coverage-etf` 279 -> 670 for the richer route/data-state support map; raised `ownership` 1,339 -> 1,509 and `ownership-13f` 1,535 -> 1,830 from recaptured fixtures under the G-BUDGET +10% headroom rule. ## 2026-06-13: external-audit resolution — correctness, provenance & coverage [#2026-06-13-external-audit-resolution--correctness-provenance--coverage] A trust pass clearing an external audit. Consumer-facing changes: * **`/v2/facts` revenue is the statement total, never a subset.** For issuers that tag a contract-revenue component (ASC-606) alongside the `Revenues` total (e.g. BRK-B), `/v2/facts` previously served the smaller component as `revenue`; it now applies the same dominated-component drop `/v2/fundamentals` uses and serves the total. * **`/v2/facts` multi-class EPS/WASO.** For multi-class issuers (GOOGL/GOOG, BRK) the grid serves the **quoted ticker's own share-class** `eps_basic`/`eps_diluted`/`shares_*_weighted` (filed two-class-method values) for the periods a per-class filing was captured, and the consolidated companyfacts figure otherwise — provenance (the exact XBRL tag) distinguishes a class value from the consolidated one. The per-class overlay is point-in-time safe: an `as_of` query only applies a per-class value the filing date proves was filed on or before that date (no look-ahead leak). * **`/v2/facts` semiannual cadence.** Foreign filers that report half-yearly (live 6-K H1 facts) now serve their H1 periods with `cadence: "semiannual"` instead of being collapsed to annual (which dropped every interim row). The `cadence` enum is widened to `quarterly | annual | semiannual` (a semiannual response previously failed response validation). * **`/v2/fundamentals` `statements_source` (additive).** Every response now carries `statements_source: "sec_edgar" | "vendor"`, labelling whether the statement rows came from the EDGAR-native store (per-value SEC provenance) or the price-vendor migration fallback for a not-yet-reconciled symbol. Treat `vendor` as lower-provenance. * **`/v2/fundamentals` `piotroski_f_score` is value-or-envelope.** When the F-Score can't be computed it is now `{available: false, reason: "insufficient_statement_history"}` in the field itself — the old `piotroski_f_score: null` + sibling `piotroski_status` pair is gone (one field, one shape). * **`/v2/profile` `description` is SEC-sourced or omitted.** The business description is the SEC 10-K Item 1 first paragraph; the price vendor's company/fund text is no longer substituted (prices-only). When no SEC description exists (ETFs, foreign issuers, pre-IPO) the field is **omitted** entirely (never a vendor substitute, never an empty string). * **`/v2/coverage` support map** now reports the four W6–W12 SEC families — `events`, `ownership`, `governance`, `facts` — each `{supported: true}` for an EDGAR-covered stock, `not_applicable_for_etf` for ETFs, `symbol_does_not_file_us_domestic_forms` for foreign issuers on `events`, `no_sec_filer_match` without a CIK. * **Supported universe** adds three multi-class tickers that were curated but unresolvable: **GOOG** (Alphabet Class C), **RUSHB**, **WLYB**. * **Smaller default payloads:** `/v2/ownership` `beneficial_ownership.history` and `/v2/price-action` `fails_to_deliver.rows` are now windowed to a recent default (the full series stays in the store) so the blocks can't grow past their token budgets. `coverage`/`coverage-etf` budgets rose to fit the four new families (133 → 161, 206 → 279). * **Public OpenAPI spec** no longer lists the internal `/version` and `/ops/freshness` routes. * **`schema_version`** bumped to `2026-06-13.1` to mark the wire-shape changes in this pass. Internal reliability (no wire change): the in-flight parse recovery and retry-requeue now fail loud (a Redis drop preserves the batch instead of losing a filing) — and the **initial** parse enqueue now fails loud the same way, so a Redis blip can no longer mark a filing warmed while its durable parse work was silently dropped; a facts-only fundamentals rebuild now **tombstones** any stale statements doc instead of leaving it readable behind a freshness-epoch bump; an Unkey `valid=false` verdict now purges the last-known-good cache so a revoked key can't be resurrected via LKG during an upstream outage; bulk SEC downloads (companyfacts/submissions/FSDS) draw a fleet request slot and trip the 403/429 breaker; the stale 4.5 GB bootstrap memory guard now reads the env-tunable 18 GiB alarm; G-OWN's expected-red coverage gap now expires; G-GOV and G-GUID fail on a silently-empty store/blind sample (and G-GUID refuses to seed its drift baseline from a degenerate near-zero parse rate); and repeated invalid API keys are negative-cached so they can't force unbounded auth round-trips. ## 2026-06-13: /v2/facts — foreign-filer currency & cadence (fixes) [#2026-06-13-v2facts--foreign-filer-currency--cadence-fixes] Two corrections for non-US (IFRS) filers on `/v2/facts`: * **Native reporting currency.** `currency_unit` now carries the filer's functional currency (e.g. `EUR`, `TWD`, `DKK`) for foreign issuers instead of mislabeling them `USD`; US-GAAP filers stay `USD`. (This also fixes a regression where a non-USD `currency_unit` could 503.) * **Reporting cadence.** Foreign issuers that report only annually/semiannually now serve flow concepts (`revenue`, `net_income`, …) at their actual cadence on any request — a default *quarterly* request previously returned empty rows because these filers have no quarterly periods. The response's `cadence` field reflects the cadence actually served. ## 2026-06-13: production-hardening pass — contract corrections & SEC-safety [#2026-06-13-production-hardening-pass--contract-corrections--sec-safety] A trust/hardening sweep over the EDGAR surfaces. Consumer-facing contract corrections: * **`/v2/events` labels filter** now documents the correct rejection: an unknown label returns **400 / `PARAM_INVALID`** (the OpenAPI parameter description previously said 422; the behavior was always 400). A doc/spec correction, no behavior change. * **`/v2/facts` `currency_unit`** now reflects the filer's native functional currency for IFRS issuers (it was hardcoded `USD`). US-GAAP filers are unaffected (still USD). * **`net_debt` envelope** now distinguishes a genuinely debt-free issuer (`no_debt`) from one whose debt component is absent from the filing (`missing_balance_sheet_components`) — previously both read as `no_debt`, which a consumer could misread as a debt-free balance sheet. * **`roic_pct` description** clarified: computed on a pre-tax operating-income basis, not after-tax NOPAT (the value was always operating-income-based; the description claimed after-tax). * **`dividends_paid` sign normalized** to a **negative cash outflow** (consistent with capex and the cash-flow statement face). The EDGAR-native path was the lone outlier — it served the XBRL `PaymentsOfDividends` magnitude as positive while the vendor path already served it negative, so the *same field* disagreed in sign depending on which path served a symbol. Now consistent across `/v2/fundamentals` and `/v2/facts`. All payout-ratio derivations take the absolute value, so they are unaffected. For symbols served from the EDGAR-native store the flip goes live as the nightly companyfacts rebuild repopulates the store; vendor-served symbols were already negative. * **Insider** cross-references corrected to "Form 4 + Form 144" (the endpoint never served Forms 3/5). Internal reliability (no wire change): every direct SEC HTTP read now draws from the shared fleet rate budget and trips the 403/429 breaker; the API binds that budget on startup; the edgartools rate partition default dropped 9 → 4 to keep the fleet under SEC's 10/s cliff; the parse queue's pop + in-flight stash is one atomic operation; ingest store writes fail loud (requeue/dead-letter) instead of silently dropping; the G-OWN 13F-plausibility and G-NPORT coverage gates now actually fail on bad/missing data; `/v2/history` consults the derived-flip approval set like the rest of the surface; and insider cache invalidation now covers every `limit` variant via the per-symbol freshness epoch. ## 2026-06-13: cover-page identity on /v2/profile — auditor, filer category, public float (additive) [#2026-06-13-cover-page-identity-on-v2profile--auditor-filer-category-public-float-additive] Stock profiles gain three cover-page identity facts, collected by parsing the latest annual filing's iXBRL cover (the dei auditor tags are NOT in the SEC's companyfacts API — this is a per-filing collection, mandatory on covers since FY2021): * `auditor` — `{name, pcaob_firm_id, location, accession, form, filed}`: the issuer's independent registered public accounting firm exactly as tagged (e.g. Ernst & Young LLP, PCAOB firm 42, for AAPL). Auditor *changes* surface on `/v2/events` (8-K Item 4.01 → `auditor_changes`). * `filer_category` — `{value, accession, form, filed}`: dei:EntityFilerCategory as filed ('Large Accelerated Filer', …), the filing-deadline/disclosure regime fact. * `public_float` — `{value_usd, as_of, accession, form, filed}`: dei:EntityPublicFloat in whole dollars. `as_of` is the SEC's measurement date (last business day of the issuer's second fiscal quarter) — months before the filing date; do not read it as current. Envelopes: `cover_page_not_yet_collected` until the symbol's first annual cover is parsed (backfill in progress); `not_disclosed_in_filing` where the form genuinely carries no such tag (pre-FY2021 covers; 20-F/40-F have no public-float disclosure). Values were verified against three hand-checked filings (AAPL/MSFT/JPM) with the public float cross-checked to the dollar against the companyfacts API. Token budget: `profile` 443 → 607 (the three identity blocks with per-block provenance), measured from the recaptured fixture +10% per the G-BUDGET baseline rule. ## 2026-06-13: token budgets — new entries for the EDGAR-Complete surfaces [#2026-06-13-token-budgets--new-entries-for-the-edgar-complete-surfaces] New budgets join `responses/coverage/token_budgets.json`, measured from captured fixtures +10% headroom per the G-BUDGET baseline rule: `ownership` (1,339), `ownership-13f` (1,600 — provisional headroom pending the full 13F bootstrap's top-20 shape), `governance` (543), `facts-max` (7,278 — the pinned 10-concepts × 12-periods × full-provenance maximum), `earnings-guidance` (1,039), `insider-intent` (2,155), `profile-etf-holdings` (701), `price-action-ftd` (945). Raised: `insider` 1,484 → 2,105 (the Form 144 `intent_to_sell` block). ## 2026-06-13: fails-to-deliver on /v2/price-action (opt-in block) [#2026-06-13-fails-to-deliver-on-v2price-action-opt-in-block] `/v2/price-action?blocks=fails_to_deliver` adds the SEC's semi-monthly CNS fails-to-deliver series for the symbol: `rows` of `{settlement_date, fails_shares, price_usd}` (newest first, \~3 months), with the block carrying its OWN vintage stamps — `as_of_settlement_date` (the latest loaded settlement date) and `data_lag_days`. The SEC publishes each half-month roughly 2–6 weeks after it ends; this block is never as fresh as the endpoint's price data, and the stamps say exactly how stale it is. A fails row is a position-level snapshot at CNS, not a flow; a date with no row means no reported fails that day. If a publication cycle is ever missed, the block refuses with `ftd_cycle_unavailable` rather than serving old rows as current. ## 2026-06-13: ETF holdings on /v2/profile — N-PORT (shape addition) [#2026-06-13-etf-holdings-on-v2profile--n-port-shape-addition] ETF profiles gain `holdings`: the fund's top-10 positions from its latest public SEC Form N-PORT filing — name, CUSIP, `weight_pct` (percent of net assets exactly as filed), `value_usd`, plus `total_holdings`, `total_assets_usd`, the report-period `as_of`, and the source accession. N-PORT holdings publish on a roughly quarterly cadence with a \~60-day lag (the `as_of` date is the honesty stamp — never read holdings as current). Resolution is series-keyed by construction (ticker → SEC seriesId), which makes the multi-fund-trust ambiguity impossible. Goldens: IVV (507 holdings), XLK (76), VTI (3,524) parsed from their filings with offline replay; the captured top holding (NVIDIA across all three) matches public reality. The honest exception: **SPY serves `{available: false, reason: "fund_files_no_nport"}`** — the SPDR S\&P 500 ETF Trust is a unit investment trust, a structure the SEC does not require to file N-PORT. That is a fact about the fund, not a gap in collection. `expense_ratio`/`aum_usd` remain intentionally absent (no reliable source yet — no field, no roadmap envelope). ## 2026-06-13: generic facts catalog — new endpoint `/v2/facts` [#2026-06-13-generic-facts-catalog--new-endpoint-v2facts] New endpoint `GET /v2/facts?symbol=AAPL&concepts=revenue,net_income,eps_diluted&periods=8q` (Starter and Builder plans): any 1–10 concepts from StockContext's curated SEC fact catalog over up to 12 quarterly or annual periods, as a columns+rows grid. The concept vocabulary is the same snake\_case set `/v2/fundamentals` speaks (collected concepts only — derived metrics like ebitda and the lease-inclusive total\_debt stay on `/v2/fundamentals`, where their composition is governed and documented). Unknown concepts come back under `unavailable_concepts` with `not_in_catalog` — the catalog is curated, not the full XBRL space, and that perimeter is explicit. Two research-grade options: `as_of=YYYY-MM-DD` serves the POINT-IN-TIME view — only facts filed on or before that date are considered, so restatements filed later are excluded (as-known-on-date research with no look-ahead); `provenance=full` adds a per-cell citation registry — accession, form, filed date, the exact XBRL tag, and a `restated` flag when an earlier filing reported a materially different value for the period. Null cells always carry a reason in the `unavailable` registry. New MCP tool: `stockcontext_facts`. ## 2026-06-13: governance & compensation — new endpoint `/v2/governance` [#2026-06-13-governance--compensation--new-endpoint-v2governance] New endpoint `GET /v2/governance?symbol=AAPL` (Starter and Builder plans): executive compensation and voting facts read from the issuer's latest proxy statement's own iXBRL (the SEC's ecd taxonomy) — never scraped prose. `pay_vs_performance` is the SEC-mandated 5-year grid in columns+rows form: compensation ACTUALLY PAID to the PEO (the ecd-defined measure including equity value changes — not the summary-comp-table total) and the other NEOs' average, against total shareholder return (value of $100 invested), the issuer's chosen peer-group TSR, and net income; null cells are explained in the `unavailable` registry. `ceo_pay_ratio` carries the issuer's own Item 402(u) ratio with components (methodologies vary across issuers — compare with care). `voting_proposals` lists the ballot; outcomes surface later on `/v2/events` (8-K Item 5.07 → `shareholder_vote_results`). Coverage: the ecd mandate era (`coverage_start` 2022-12-16 by fiscal year end). An older proxy returns `proxy_has_no_ecd_xbrl` with its accession — deeper compensation history would be scrape-grade, which this product refuses by design. Goldens: five cross-sector proxies (AAPL/MSFT/JPM/WMT/XOM) where the parse equals an independent extraction from the proxy's raw ecd iXBRL on every PEO/NEO/TSR value; pay ratios match public reality (AAPL 533:1, MSFT 480:1, JPM 363:1, XOM 181:1; WMT's untagged ratio envelopes honestly). New MCP tool: `stockcontext_governance`. ## 2026-06-13: company-stated guidance on /v2/earnings — the estimates answer (shape addition) [#2026-06-13-company-stated-guidance-on-v2earnings--the-estimates-answer-shape-addition] `/v2/earnings` gains `company_guidance`: forward guidance the COMPANY stated in its 8-K press releases, extracted under a strict serve-or-refuse rule — a number is served ONLY when its period, metric, value(s), unit, and basis were EACH independently anchored in the release text. Statements that miss an anchor (an unqualified "EPS" range — ambiguous between GAAP and adjusted in real releases — or "EBITDA of approximately 68% of revenue") are counted in `unparsed_signals` instead of being guessed at; read the release via the accession for those. Refusal is deliberate behavior, not failure. Rows carry `period` in the company's OWN fiscal vocabulary (`q3_2026`, `fy_2027` — a retailer's fy\_2026 may end in calendar 2027; never silently mapped onto calendar time), `period_as_stated` (the verbatim anchor phrase), `metric`, `basis` (gaap | non\_gaap, anchored by the metric phrase itself), `unit` (usd | usd\_per\_share | pct), `low`/`high` (equal for point guidance). Once a guided GAAP quarter is reported, the filed actual joins the row (`actual.value`, `within_range`, `vs_midpoint_pct`) — pure arithmetic against the company's own range, never a beat/miss versus analyst consensus (which this product excludes by design); a company non-GAAP guide is never compared against our GAAP actual. Coverage: the trailing \~120 days of 8-K press releases (backfill) plus everything filed from 2026-06-12 onward (event-driven, minutes after acceptance). `{available: false, reason: "no_guidance_detected"}` means no guidance language was found — most issuers publish none. Goldens: 20 real filings captured with machine-verified anchors (every served value literally present in the release text), including ≥5 refusal cases; the nightly G-GUID gate samples 25 fresh exhibits and alarms on parse-rate drift. ## 2026-06-13: institutional 13F positioning on /v2/ownership (shape addition) [#2026-06-13-institutional-13f-positioning-on-v2ownership-shape-addition] `/v2/ownership` gains `institutional_13f`: Form 13F positioning in the security for the latest collected quarter — `total_value_usd`, `total_shares`, `institution_count`, `concentration_hhi` (Herfindahl over the 13F-filer value shares — the institutional holder base, not total ownership), the fixed top-20 managers (`pct_of_13f_total` is each manager's share of 13F-reported value), and a `qoq` comparison against the prior quarter. Honesty rails, all explicit in the payload: 13F positions are as of `as_of_quarter_end` and filed up to 45 days later (`filed_through` stamps how complete the quarter's accumulation is) — never current positioning; value deltas mix flows AND price moves; positions are in THIS listed security/class only (an issuer's other classes are separate securities — e.g. Alphabet Class C positions do not appear under GOOGL); a reported share sum above 105% of shares outstanding refuses the whole block with `ownership_aggregate_implausible` rather than serving an implausible number. Amendment semantics follow the SEC's own taxonomy: RESTATEMENT replaces a manager's filing, NEW HOLDINGS adds to it. Aggregates were verified by parsing filings live and matching the SEC's quarterly Form 13F Data Set flattening of the same accessions exactly (Berkshire Hathaway among the goldens); the derived AAPL top holders (BlackRock / Vanguard / State Street / Geode / FMR) match public reality. ## 2026-06-13: Form 144 intent-to-sell on /v2/insider (shape addition) [#2026-06-13-form-144-intent-to-sell-on-v2insider-shape-addition] `/v2/insider` gains `intent_to_sell`: Form 144 NOTICES filed in the trailing 90 days, newest first — `person_selling`, `security_class`, `units_to_be_sold`, `market_value_usd`, `broker`, `approx_sale_date`, `is_10b5_1_plan`, and the source accession. A Form 144 is forward-looking: an affiliate files it BEFORE selling restricted/control stock (the notice is valid \~90 days; filing one does not require completing the sale). The executed trades, when they happen, appear in `recent_transactions` as Form 4 sales — match person and dates to link a notice to its execution. Coverage: the electronic-filing era (2023-04 onward). Values verified against two hand-checked notices with an independent raw-XML cross-check. ## 2026-06-13: beneficial ownership — new endpoint `/v2/ownership` [#2026-06-13-beneficial-ownership--new-endpoint-v2ownership] New endpoint `GET /v2/ownership?symbol=SAKR` (Starter and Builder plans): SEC Schedule 13D/13G beneficial ownership for a stock. `beneficial_ownership.holders` lists the current >5% reporting persons, largest first — name, CIK (when filed), `pct_of_class` exactly as filed, `aggregate_shares`, the SEC reporting-person type code (IA/PN/HC/IN/…), whether the stake is on the activist schedule (13D) or the passive one (13G), and the source accession. The latest filing per reporting person wins; a filing reporting under 5% removes the person from holders (its history row remains). `history` carries the filing trail (initiations, amendments, exits; up to 25 rows newest-first); `unparsed_filings` is the honest perimeter — stake filings the structured parser cannot read (legacy-era documents without XML), disclosed with their accessions rather than silently dropped. Coverage: the structured-data era (`coverage_start` 2024-12-18, when the SEC mandated XML for these schedules); both EDGAR form spellings are captured (`SCHEDULE 13D` and the legacy-styled `SC 13D/A` entries that still appear). `pct_of_class` is as-filed at `event_date` — struck at the filing's own share count, not adjusted for later dilution. While a symbol's backfill is pending, `beneficial_ownership` is `{available: false, reason: "stake_data_not_yet_collected"}`. Values were verified against hand-checked filings with an independent raw-XML cross-check, including a real multi-filing amendment chain. New MCP tool: `stockcontext_ownership`. ## 2026-06-13: 5% beneficial-ownership stake events (additive) [#2026-06-13-5-beneficial-ownership-stake-events-additive] Schedule 13D/13G filings about an issuer now appear in its `/v2/events` stream: `activist_stake_initiated` (Schedule 13D — the filer must use 13D when holding with intent to influence control), `passive_stake_initiated` (Schedule 13G — passive/institutional), `stake_amended` (amendments to either). Both the post-2024-12-18 form spellings (`SCHEDULE 13D`) and the legacy spellings still appearing in the feed (`SC 13D/A`) are classified. The active/passive distinction is the SEC's own — which schedule the filer is required to use — not our judgment. ## 2026-06-13: SEC comment letters (additive) [#2026-06-13-sec-comment-letters-additive] SEC review correspondence joins both filing surfaces. `/v2/filings` `form=` accepts `UPLOAD` (the SEC staff's comment letter) and `CORRESP` (the company's reply) — rows carry the standard metadata (date, accession, URL); letter text is link-only via the row URL. `/v2/events` classifies both forms as `sec_comment_letter`. The SEC releases this correspondence no earlier than \~20 business days after a review closes — the dates are release dates, not the dates the questions were asked. ## 2026-06-13: /v2/events classifies the full SEC event surface (row shape changed) [#2026-06-13-v2events-classifies-the-full-sec-event-surface-row-shape-changed] `schema_version` is now `2026-06-13`. `/v2/events` grows from an 8-K stream into the classified stream of a stock's recent SEC events. **Row shape changed**: `items` is now the raw SEC item-code strings exactly as filed (`["2.02", "9.01"]` — previously `{item, label}` objects; matching the `items` shape `/v2/filings` rows already use), and every row gains `labels` — machine-readable classification tokens (for 8-Ks one label per distinct item code; for other forms exactly one). New event families in the stream, each classified by the form the SEC received (facts, never judgments): late filings (`NT 10-K`/`NT 10-Q`/`NT 20-F` → `late_filing_notice`), delisting (`25`/`25-NSE` → `delisting_notice`), deregistration (`15-12B`/`15-12G`/`15-15D`/`15F-*` → `deregistration`), offerings and dilution (`S-1`/`F-1` → `securities_registration`, `S-3`/`S-3ASR`/`F-3`/`F-3ASR` → `shelf_registration`, `S-8`/`S-8 POS` → `employee_plan_registration`, `424B1`–`424B5` → `prospectus_offering`), and the M\&A family (`S-4` → `merger_registration`, `DEFM14A`/`DEFM14C` → `merger_proxy`, `SC TO-T`/`SC TO-I` → `tender_offer`, `SC 13E3` → `going_private`, `425` → `merger_communication`). Additionally, `going_concern_disclosure` rows appear when `us-gaap:SubstantialDoubtAboutGoingConcernTextBlock` is detected in a new 10-K/10-Q's XBRL (detection live from 2026-06-12 onward; historical backfill planned). The full label vocabulary is frozen and documented; every label is pinned in CI against real filings. New optional `labels=` parameter filters the stream to comma-separated classification tokens (unknown tokens are rejected with 400) — useful where routine filings dominate, e.g. issuers with structured-note shelf programs (large banks) file `prospectus_offering` daily. Event-family filings now also bump the per-symbol payload epoch, so the stream reflects new filings within minutes of EDGAR acceptance instead of waiting out the cache TTL. ## 2026-06-12: derived enterprise value everywhere — vendor EV retired from current values [#2026-06-12-derived-enterprise-value-everywhere--vendor-ev-retired-from-current-values] Enterprise value on `/v2/snapshot`, `/v2/valuation`, `/v2/compare`, and `/v2/priced-in` is now ALWAYS derived as current share-basis-verified market cap + net debt from the latest filed quarterly balance sheet — the vendor-EV fallback is deleted. The four surfaces agree on EV by construction. When net debt is incomputable from the filed balance sheet, EV (and the EV multiples) ship an honest `{available: false, reason: "net_debt_unavailable"}` envelope instead of silently switching to a vendor number on a possibly different share basis. **`/v2/priced-in` anchor shape changed**: `anchor.enterprise_value_as_of` is REMOVED (it named a vendor row's vintage). The anchor now carries `net_debt_period_end` (the filed balance-sheet period supplying the net-debt leg) and `share_count_basis` (same vocabulary as `/v2/valuation`). A share-basis refusal now refuses the reverse-DCF anchor with `share_price_basis_unverified` — the model never discounts against an EV on an unverified basis. `as_of`/`freshness` are now quote-anchored (a market-hours response says `intraday`), matching `/v2/valuation`. ## 2026-06-12: valuation history series on /v2/history (additive) [#2026-06-12-valuation-history-series-on-v2history-additive] `/v2/history` `series=` accepts three valuation series: `pe_ttm`, `ps_ttm`, `ev_ebitda_ttm` — the percentile receipts behind `vs_own_history`. Daily/weekly cadence only. Source: StockContext's SEC-EDGAR-derived daily store — close(t) × point-in-time shares with TTM denominators joined on each filing's FILED date (no look-ahead; values step only at filing breakpoints). Cells are null where a day's inputs are unavailable (e.g. negative TTM earnings); a symbol whose derived history is not yet built returns `{available: false, reason: "derived_history_not_available"}`. The docs example for these series lands after the first nightly store rebuild publishes the new columns. ## 2026-06-12: token budgets — new entries and small raises [#2026-06-12-token-budgets--new-entries-and-small-raises] Late-day recapture on the derived-EV release: `priced-in` 741 → 750 (the anchor's `net_debt_period_end` + `share_count_basis` fields); `filing-diff`, `fundamentals`, `insider`, `profile-etf` moved ≤2 tokens from live-data drift. Two new budgets join `responses/coverage/token_budgets.json`: `events` (1,256) and `filing-diff` (6,310), measured from the captured fixtures +10% headroom per the G-BUDGET baseline rule. Existing budgets raised for the additive blocks shipped this release: `fundamentals` 6,993 → 7,193 (the `forensics` block), `insider` 1,465 → 1,484 (distinct-buyer counts), `snapshot` 622 → 635 (the `events` block), `profile` 434 → 443 (SIC provenance fields). `calendar`, `earnings`, `filings`, and `history` moved by ≤1 token from live-data drift in the recaptured fixtures. ## 2026-06-12: filing section diffs — new endpoint `/v2/filings/{accession}/diff` (additive) [#2026-06-12-filing-section-diffs--new-endpoint-v2filingsaccessiondiff-additive] New endpoint `GET /v2/filings/{accession}/diff` (Starter and Builder plans): paragraph-level diff of one filing's `risk_factors` and `mda` sections against the same issuer's PRIOR filing of the same form family (10-K vs prior 10-K, 10-Q vs prior 10-Q; amendments diff within their family). The response ships ONLY changed paragraphs — `{section, index, change, text}` entries where `change` is `added`, `removed`, or `modified` — never both full texts. `added`/`modified` carry this filing's paragraph (index into this filing's section); `removed` carries the prior filing's paragraph (index into the prior section). A textually identical section is `{changed: [], total_changes: 0}` — the empty diff is the fact. Paragraph text caps at 1200 characters (`trimmed: true`); each section caps at 80 entries (`truncated: true`, with `total_changes` keeping the full count). Per-section unavailability is an honest envelope: `no_prior_filing_of_form`, `section_not_extractable`, `prior_section_not_extractable`, `diff_not_supported_for_form` (8-K and other non-periodic forms), `sec_submissions_not_cached`, `symbol_unresolved_from_accession`. Top-level `prior_accession` and `prior_filing_date` name the comparison base (or carry the same envelope). Diffs are computed once per accession and cached long — filings are immutable — and are precomputed at ingest for newly filed 10-K/10-Q accessions. New MCP tool: `stockcontext_filing_diff`. ## 2026-06-12: classified 8-K event stream — new endpoint `/v2/events` (additive) [#2026-06-12-classified-8-k-event-stream--new-endpoint-v2events-additive] New endpoint `GET /v2/events?symbol=AAPL&limit=20` (Starter and Builder plans): a classified stream of a stock's recent 8-K / 8-K/A current reports, newest first. Each event carries `date`, `accession`, `form`, and `items` — the SEC item codes the filing disclosed, each with a fixed machine-readable label (`2.02` → `results_of_operations`, `5.02` → `officer_director_changes`, `7.01` → `regulation_fd_disclosure`, …). Labels are the SEC's own 8-K item classifications mapped to stable snake\_case tokens — disclosure categories, never judgments; an unrecognized item code passes through as `unclassified_item`. `limit` defaults to 20 (1–50). The stream is a pure projection of the cached SEC submissions index — no request-path EDGAR fetch — so while that cache is cold, `events` is `{available: false, reason: "sec_submissions_not_cached"}` rather than a misleading empty list. ETFs and foreign private issuers (which file 6-K, not 8-K) return the same unsupported envelope as `/v2/filings`. New MCP tool: `stockcontext_events`. ## 2026-06-12: distinct-buyer counts on /v2/insider (additive) [#2026-06-12-distinct-buyer-counts-on-v2insider-additive] No version bump — additive change. * `/v2/insider`: `summary_90d` gains `distinct_buyers_30d` and `distinct_buyers_90d` — integer counts of DISTINCT reporting owners with at least one open-market purchase (Form 4 transaction code `P`) filed in the trailing 30/90 days. Counts of buyers, not transactions, and never a thresholded "cluster detected" flag — apply your own threshold (e.g. `>= 3` buyers in 30 days as a cluster-buy signal). Computed over the same scanned Form 4 window as the rest of `summary_90d`; for extremely heavy filers a `scanned_latest_200_filings_only` notice means the window may be truncated. ETFs and other unsupported symbols keep the existing whole-endpoint unsupported envelope. ## 2026-06-12: forensics block on /v2/fundamentals (additive) [#2026-06-12-forensics-block-on-v2fundamentals-additive] New `forensics` block (and `blocks=forensics` projection value): earnings-quality facts — `cash_conversion` (TTM OCF / TTM net income), `accruals_ratio_pct` (Sloan accruals over average assets), the cash-conversion-cycle legs (`days_sales_outstanding`, `days_inventory`, `days_payables`, `cash_conversion_cycle_days`, each `{value, yoy_delta}`), `sbc_pct_revenue` / `sbc_pct_ocf`, `diluted_shares_cagr_3y_pct`/`_5y_pct`, and `revenue_per_share_cagr_3y_pct` / `fcf_per_share_cagr_3y_pct`. Facts and arithmetic only — composition, never verdicts. Banks/brokers/insurers carry `{available: false, reason: "financial_company_metric_not_meaningful"}` by construction. ## 2026-06-12: misclassified financial suppressions corrected [#2026-06-12-misclassified-financial-suppressions-corrected] 21 issuers were inheriting financial-company metric suppression (FCF yield, EV multiples, priced-in refusals) from stale vendor industry labels — mostly ticker-reuse artifacts that classified Sweetgreen (SG) as an insurer, AbCellera (ABCL) and Astera Labs (ALAB) as banks, Archer Aviation (ACHR) as property & casualty insurance, and similar. These now ship their real FCF/EV metrics: ABCL, ACHR, ALAB, APLD, AUC, CYN, DFIN, DRCT, GOLD, HGBL, LSH, PROP, ROMA, SG, STI, SWIM, TGL, UPC, WAI, XZO, ZBAI. Four hand-verified financial issuers whose SEC SIC misses the standard ranges stay suppressed (Bank OZK, FS Credit Opportunities, Scully Royalty, GigCapital8). Cached responses converge within each endpoint's normal refresh window. ## 2026-06-12: EDGAR-native classification (sector vocabulary change, new SIC fields) [#2026-06-12-edgar-native-classification-sector-vocabulary-change-new-sic-fields] `schema_version` is now `2026-06-12`. * `/v2/snapshot` and `/v2/profile`: **`sector` vocabulary changed** to StockContext's curated GICS-style classification — the 11 standard sector names (e.g. `Information Technology` where you previously saw `Technology`, `Financials` instead of `Financial Services`, `Health Care` instead of `Healthcare`, `Consumer Discretionary`/`Consumer Staples` instead of `Consumer Cyclical`/`Consumer Defensive`). If you branch on sector strings, update your matchers. * `/v2/snapshot` and `/v2/profile`: **`industry` source changed** — it now renders the issuer's SEC SIC classification (e.g. `Electronic Computers` for AAPL, previously the commercial-taxonomy `Consumer Electronics`). Coarser than commercial taxonomies but a citable primary-source fact; **new fields** `sic` (the 4-digit code as filed) and `sic_description` (the SEC's verbatim string) ship alongside as provenance. * `/v2/snapshot` stock `name` now comes from the SEC-sourced curated universe — the same name `/v2/search` and `/v2/coverage` return (minor styling differences, e.g. `Apple Inc.` with the period). * `/v2/profile`: `country` and `website` are now sourced from the issuer's SEC submissions (business address and issuer-stated website) instead of vendor metadata. * Financial-company metric suppression (FCF yield, EV multiples, priced-in refusals for banks/brokers/insurers) is now driven by a curated SIC-seeded classification table. The suppressed set is **identical** to the previous behavior — no symbol's suppression changed in this release. * These changes complete the retirement of vendor classification metadata: every identity/classification fact now comes from SEC EDGAR or StockContext's curated tables. ## 2026-06-11: response token budgets enforced (G-BUDGET) [#2026-06-11-response-token-budgets-enforced-g-budget] No payload change — a new gate on ourselves, documented here because its rule is a customer promise: every endpoint's response now has a frozen token budget (`responses/coverage/token_budgets.json`, o200k\_base over the wire JSON), checked against the docs fixtures in CI and against live production responses nightly. Budgets only ratchet **down**; any raise requires an entry on this page. Baseline set 2026-06-11 from the measured post-W2 fixtures +10% headroom. ## 2026-06-11: z-scores, compare context columns, fundamentals block projection (additive) [#2026-06-11-z-scores-compare-context-columns-fundamentals-block-projection-additive] No version bump — additive changes. * `/v2/snapshot`: quick multiples' own-history micro-tag gains the z-score dialect — `{value, vs_5y, z_5y}` (`z_3y` when the label window fell back to 3y). The z-score is the same self-referential rank as `vs_5y`, expressed in standard deviations from the symbol's OWN window mean (sample std) — never cheapness versus peers. * `/v2/valuation`: `vs_own_history` gains `z_3y` / `z_5y` / `z_10y`, one per qualifying window (the same ≥80%-span/≥12-point qualification as the percentile map). Omitted for a constant distribution — z is undefined there, never 0. * `/v2/compare`: `fields=` accepts four opt-in own-history context columns — `pe_ttm_vs_5y`, `ps_ttm_vs_5y`, `pb_vs_5y`, `ev_ebitda_ttm_vs_5y` — projecting the snapshot `vs_5y` band label flat into the table. Defaults unchanged. * `/v2/fundamentals`: new `blocks=` query parameter — a comma-separated subset of `income_statement`, `balance_sheet`, `cash_flow`, `ttm`, `trajectory` projects the response to just those blocks (e.g. `blocks=ttm` is a small fraction of the full payload's tokens). Omit for the full response; the default is unchanged. ## 2026-06-11: snapshot filing events (additive) [#2026-06-11-snapshot-filing-events-additive] No version bump — additive change. * `/v2/snapshot`: new `events` block for stocks: `latest_filing` (`{form, filing_date, accession}` — the most recent company disclosure filing: 10-K/10-Q/8-K families, 20-F/40-F/6-K for foreign private issuers; insider Forms 3/4/5 and registration paperwork are deliberately excluded) and `next_earnings_date_estimated` (projected from the issuer's own fiscal period-end cadence + typical filing lag, reconciled against its latest earnings-release 8-K — identical to the `/v2/calendar` projection). Both fields are value-or-envelope (`sec_submissions_not_cached`, `no_sec_filer_match`, `no_recent_disclosure_filings`, `insufficient_filing_cadence`). ETF responses carry `events: { available: false, reason: "not_applicable_for_etf" }`. ## 2026-06-11: payout verdict and trailing PEG removed [#2026-06-11-payout-verdict-and-trailing-peg-removed] `schema_version` is now `2026-06-11`. * `/v2/dividends`: **removed** `dividend_health.payout_safety`; StockContext no longer ships a payout-safety letter grade. The underlying facts remain under `dividend_health.track_record` and `dividend_health.payout_components`. **Renamed** `dividend_health.grade_components` to `dividend_health.payout_components`. * `/v2/snapshot`: **removed** `fundamentals_quick.peg_1y` for stocks and ETFs. * `/v2/valuation`: **removed** `multiples.peg_1y`. * `/v2/compare`: **removed** `peg_1y` from the allowed `fields` list. Defaults are unchanged, but `fields=peg_1y` now returns `400 PARAM_INVALID`. ## 2026-06-10: value-or-envelope consolidation, honest freshness [#2026-06-10-value-or-envelope-consolidation-honest-freshness] `schema_version` is now `2026-06-10`. * **Renamed** the `/v2/technicals` `relative_strength` keys: `vs_spy_1m_pct` → `vs_spy_pct_1m`, `vs_spy_3m_pct` → `vs_spy_pct_3m`, `vs_spy_1y_pct` → `vs_spy_pct_1y`, `vs_sector_1y_pct` → `vs_sector_pct_1y`. On `/v2/snapshot`, `performance.vs_sector_1y_pct` → `vs_sector_pct_1y`. * **Removed** the `vs_sector_status` and `ratio_to_sector_status` sibling objects everywhere. `vs_sector_pct_1y` (snapshot, technicals) and `volume.ratio_to_sector_avg` (price action) are now value-or-envelope: a number, or `{ available: false, reason: "not_applicable_for_etf" | "sector_benchmark_unavailable" }`. On snapshot/technicals a bare `null` means only one thing: insufficient trading history for the window. * `/v2/dividends`: new `current.yield_basis` (`"forward_annualized"` | `"trailing_12m"`), always present when `yield_pct` is numeric, labels which convention produced the yield. Irregular payers now get `{ available: false, reason }` envelopes — never bare `null` — on `annual_rate`, `annual_rate_forward_estimated`, `next_ex_date_estimated`, and `next_payment_amount_estimated` (reasons `irregular_cadence_no_annual_rate`, `irregular_cadence_no_estimate`); lapsed payers envelope the projection fields with reason `dividend_lapsed`. **Removed** `payout_safety.rationale` (a static string): `payout_safety` is now `{ grade }` or an unavailable envelope. * `/v2/earnings`: `eps_actual`, `revenue_actual`, and the `vs_trend` baseline fields are now value-or-envelope (reasons `not_reported_in_filed_statements`, `insufficient_reported_history`). YoY comparisons state why they are unavailable: `no_prior_period`, `not_reported_in_filed_statements` (current quarter unreported), or `missing_prior_quarter_value`. * Freshness contract tightened: `earnings`, `insider`, `calendar`, `filings`, `dividends`, and `priced-in` **never** report `freshness: "intraday"` — they are `end_of_day` (or `degraded`/`unsupported`). The price-flavored endpoints (`snapshot`, `valuation`, `technicals`, `price-action`, `history`, `compare`) keep their freshness behavior — intraday-capable where they were before (`/v2/history` stays `end_of_day`-only) — and cached serves now re-stamp `freshness` truthfully at serve time: a body baked `intraday` can serve as `end_of_day` or `stale` once it ages. `market_status` is always the serve-time market state. ## 2026-06-06: contract corrections [#2026-06-06-contract-corrections] * `/v2/compare` now accepts **2–12** symbols per request (was 2–4). 13 or more returns `400 PARAM_INVALID`; one returns `400 PARAM_INVALID`. * `PARAM_INVALID` messages now **name the offending parameter** and, for enum/range errors, list the allowed values, so you can branch on the message instead of re-deriving the contract. * Documented the `NOT_FOUND` (`404`) error code returned for unmatched routes (a typo or wrong base path), distinct from `SYMBOL_UNKNOWN`. * Rate-limit headers (`X-RateLimit-*`) are now confirmed on **every post-auth response**, including `400`, `403`, and `404 SYMBOL_UNKNOWN`; only a pre-auth `401` and an unmatched-route `404 NOT_FOUND` omit them. * Snapshot/technicals expose `vs_sector_1y_pct` (1-year return versus the symbol's sector benchmark ETF), with a `vs_sector_status` envelope when unavailable. * `GET /version` (root, not under `/v2`) returns the running build (`commit`, `branch`, `built`). ## 2026-06-05: v2 reference rebuilt [#2026-06-05-v2-reference-rebuilt] * Every response example in these docs is now a captured production payload, not a hand-written sample. * Every response field is documented. * The error envelope schema is published: see [response model](/docs/reference/response-model) and [error codes](/docs/reference/error-codes). * Request and response samples ship in both Python and TypeScript. ## How we ship changes [#how-we-ship-changes] **Additive changes** (new response fields, new endpoints) arrive without notice and without a version bump. Parse defensively: ignore fields you do not recognize and never fail on an unexpected key. A client that tolerates unknown fields keeps working as the API grows. **Breaking changes** (removing or renaming a field, changing a type, narrowing behavior) get a dated entry here and advance the version. If it is not written on this page, it did not break. # Coverage and known gaps (/docs/reference/coverage-and-gaps) StockContext covers **5,628 symbols**: **5,571 stocks plus 57 ETFs**, listed on NYSE, NASDAQ, and the ETF venues NYSE ARCA and BATS (verified 2026-06-13). It is a supported-symbol API, not a market-wide dump: a symbol either resolves to data or it does not. Call `/v2/search` to discover symbols and `/v2/coverage` to confirm what a symbol supports before you build a workflow on it. ## The universe [#the-universe] The `/v2/coverage` support map reports eighteen data families: snapshot, profile, fundamentals, valuation, priced-in (reverse-DCF), earnings, dividends, technicals, price action, history, calendar, filings, insider, events, facts, plus the private-beta families ownership, governance, and segments. Each family carries `public_visibility`: `public_core` is safe for normal analyst-agent workflows, `private_beta` is callable only for explicitly enabled/private use, and `internal_only` is not part of the public product surface. The public MCP and docs surface exposes 21 core routes/tools. * **4,446 US-domestic filers** route-support the public SEC core. Store-backed SEC facts can still report `available_now: false` until the per-symbol store has been built. * **1,125 foreign private issuers (\~20%)** file 20-F / 40-F / 6-K instead of US-domestic forms. Domestic-form families such as filings, insider, and events return `unsupported` for them with reason `symbol_does_not_file_us_domestic_forms`; SEC facts can still work where filings expose usable XBRL. * **57 ETFs** carry market and dividend data but no issuer-level financials. Their non-applicable families return `unsupported` with reasons like `not_applicable_for_etf`. Daily price history reaches back up to 30 years, bounded by source availability per symbol (check `coverage.start`/`coverage.end` on `/v2/history`). ## What `/v2/coverage` returns [#what-v2coverage-returns] Coverage is a per-symbol support map. Use `available_now` before calling a route. `supported` is kept for back-compat and means route-level support only. For a US-domestic stock, route-level support is true. Store-backed SEC families also disclose whether the backing store is built: ```json title="GET /v2/coverage?symbol=AAPL (excerpt)" { "data": { "symbol": "AAPL", "asset_type": "stock", "cache_age_seconds": 0, "support": { "snapshot": { "supported": true, "route_supported": true, "quality_grade": "production_core", "public_visibility": "public_core", "available_now": true }, "filings": { "supported": true, "route_supported": true, "quality_grade": "production_core", "public_visibility": "public_core", "available_now": true }, "facts": { "supported": true, "route_supported": true, "quality_grade": "production_core", "public_visibility": "public_core", "data_collected": true, "available_now": true }, "ownership": { "supported": true, "route_supported": true, "quality_grade": "production_beta", "public_visibility": "private_beta", "data_collected": true, "available_now": true }, "segments": { "supported": true, "route_supported": true, "quality_grade": "production_beta", "public_visibility": "private_beta", "data_collected": true, "available_now": true } } } } ``` For an ETF, the issuer-level families come back unsupported with a machine-readable `reason` you can branch on: ```json title="GET /v2/coverage?symbol=SPY (excerpt)" { "data": { "symbol": "SPY", "asset_type": "etf", "cache_age_seconds": 0, "support": { "snapshot": { "supported": true, "route_supported": true, "public_visibility": "public_core", "available_now": true }, "fundamentals": { "supported": false, "route_supported": false, "data_collected": false, "available_now": false, "reason": "etf_no_standalone_financial_statements", "blocker": "etf_no_standalone_financial_statements" }, "filings": { "supported": false, "route_supported": false, "data_collected": false, "available_now": false, "reason": "not_applicable_for_etf", "blocker": "not_applicable_for_etf" }, "facts": { "supported": false, "route_supported": false, "public_visibility": "public_core", "data_collected": false, "available_now": false, "reason": "not_applicable_for_etf", "blocker": "not_applicable_for_etf" }, "segments": { "supported": false, "route_supported": false, "public_visibility": "private_beta", "data_collected": false, "available_now": false, "reason": "not_applicable_for_etf", "blocker": "not_applicable_for_etf" } } } } ``` For public-agent workflows, branch on both `support[family].public_visibility == "public_core"` and `support[family].available_now`. A foreign-issuer stock looks like AAPL above except domestic-form families such as `filings`, `insider`, and `events` carry `supported: false` with reason `symbol_does_not_file_us_domestic_forms`. ## Where the data ends [#where-the-data-ends] StockContext is focused on equity context, not a trading or market-intelligence platform, so an agent always knows exactly where the data ends and never guesses. None of these exist in v2, so do not imply them in UI copy, examples, or generated summaries: * options data * L2 / order-book depth * global equities (US listings only) * analyst consensus and price targets * earnings-call transcripts * news * order execution * ETF AUM or expense ratios (top holdings are source-state dependent — see `/v2/profile` `holdings` and honor unavailable reasons) * sector or peer medians (each valuation multiple is ranked against the symbol's own 3/5/10-year history only, never against peers or a fair-value target) Market data comes from licensed market-data sources; filings, filing sections, and insider activity come from SEC EDGAR. Provider names are not embedded in payloads. ## Cold EDGAR latency [#cold-edgar-latency] The first read of a given filing pulls from EDGAR and can take up to \~20 seconds for a large 10-K; repeat reads return from cache in well under a second. Listing filings is fast either way: the cold cost is per accession, when metadata and sections are first fetched. Subsequent calls for that company are served from cache and return fast. Design for the cold hit: use a loading state or a background job for SEC-heavy work, control concurrency when you fan out across many filers, and cache successful responses. Retry only retryable errors; never retry an `unsupported` filings or insider result, because the answer will not change. See [error codes](/docs/reference/error-codes) for which statuses retry. # Error codes (/docs/reference/error-codes) Failures use one envelope. Retry only when `error.retryable` is `true`. ```json { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please slow request rate and retry.", "retryable": true } } ``` `code` is the stable identifier to branch on; `message` is human-readable and may change. The table below carries the live messages captured against production. | Code | HTTP | Retryable | What to do | | --------------------------- | ---: | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `UNAUTHORIZED` | 401 | No | Send a valid key in the `X-API-Key` header. Message: "Missing or invalid X-API-Key header." A revoked key lands here within about a minute of revocation. Do not retry the same key. | | `PLAN_UPGRADE_REQUIRED` | 403 | No | The route is gated above the key's plan. Message: "This endpoint requires Starter or Builder. Free includes search, coverage, snapshot, and valuation." Upgrade or stay on the four Free routes. See [plans and limits](/docs/reference/plans-and-limits). | | `SYMBOL_INVALID` | 400 | No | The symbol failed grammar. Message: "Symbol must match `^[A-Z0-9-]{1,8}$` with dash-suffix share classes like BRK-B." Normalize before sending (`BRK.B` -> `BRK-B`). | | `SYMBOL_UNKNOWN` | 404 | No | Well-formed symbol, not in the universe. Message: "Symbol 'ZZZQ' is not in the supported universe. Use /v2/search to discover supported tickers." Resolve it via `/v2/search` or `/v2/coverage`. | | `NOT_FOUND` | 404 | No | The request path is not a StockContext route, a typo or the wrong base path. Message: "Endpoint not found." Distinct from `SYMBOL_UNKNOWN` (which is a valid route with an unknown symbol). This 404 fires before key verification, so it carries **no** rate-limit headers. | | `PARAM_INVALID` | 400 | No | A query parameter is missing, unknown, or out of range. The message **names the offending parameter** and, for enum/range errors, lists the allowed values: e.g. "range '99z' is not a recognized window; allowed: 1m, 3m, 6m, ...", "Unknown query param(s): 'bogusparam'. Allowed params: symbol.", "Invalid request parameters: query param 'symbol': field required." Branch on the named parameter. Duplicate params are tolerated (last value wins: `?symbol=AAPL&symbol=MSFT` returns MSFT). | | `RATE_LIMITED` | 429 | Yes | Per-minute, daily, or monthly limit hit. Honor `Retry-After` (seconds), lower concurrency, and cache repeat reads. Limits are in [plans and limits](/docs/reference/plans-and-limits). | | `UPSTREAM_UNAVAILABLE` | 503 | Yes | A market-data or SEC-backed source is briefly unavailable with no usable cache. Retry with backoff; keep first-hit SEC paths tolerant of latency. | | `AUTH_UPSTREAM_UNAVAILABLE` | 503 | Yes | Key verification upstream is briefly unavailable. Retry shortly; the response sends `Retry-After: 5`. | | `PREREQUISITE_MISSING` | 409 | No | **MCP resources only, never a REST endpoint.** A resource was read before the tool call that populates it. Run the prerequisite tool first, then read the resource. | The plan gate runs before parameter validation: on a Free key, a bad parameter on a paid route returns `403 PLAN_UPGRADE_REQUIRED`, not `400`. `PARAM_INVALID` now names the offending parameter (and usually its allowed values), so you can branch on it; the per-endpoint pages remain the authority on the full contract: allowed parameter names, ranges, and enum values (form lists, `range`/`cadence` values, the 2–12 symbol bound on `/v2/compare`, and so on). ## The 429, with headers [#the-429-with-headers] Successful responses and every post-auth error (`400 PARAM_INVALID`, `403 PLAN_UPGRADE_REQUIRED`, `404 SYMBOL_UNKNOWN`, `429 RATE_LIMITED`) carry `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` (Unix epoch seconds); track your budget from all of them, not just successes. Only failures that never reach key verification omit them: a pre-auth `401 UNAUTHORIZED` and an unmatched-route `404 NOT_FOUND`. A 429 adds `Retry-After` in seconds. This is a live rate-limit response from `/v2/snapshot`: ```http title="HTTP 429: headers + body" HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 30 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1780669200 Retry-After: 21 { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please slow request rate and retry.", "retryable": true } } ``` `Retry-After: 21` is the live-captured wait; sleep at least that long before retrying. If `Retry-After` is absent on a retryable error, fall back to exponential backoff with jitter. ## A retry loop that honors Retry-After [#a-retry-loop-that-honors-retry-after] Retry only when `retryable` is `true`, prefer the server's `Retry-After`, and cap attempts so a sustained outage does not spin forever. ```python title="retry.py" import time import httpx RETRYABLE = {"RATE_LIMITED", "UPSTREAM_UNAVAILABLE", "AUTH_UPSTREAM_UNAVAILABLE"} def get(client: httpx.Client, path: str, params: dict, max_attempts: int = 4) -> dict: for attempt in range(max_attempts): r = client.get(path, params=params) body = r.json() if "data" in body: return body["data"] err = body["error"] last = attempt == max_attempts - 1 if err["code"] not in RETRYABLE or last: raise RuntimeError(f"[{err['code']}] {err['message']}") retry_after = r.headers.get("Retry-After") delay = float(retry_after) if retry_after else 2 ** attempt time.sleep(delay) raise RuntimeError("exhausted retries") ``` ```ts title="retry.ts" const RETRYABLE = new Set([ "RATE_LIMITED", "UPSTREAM_UNAVAILABLE", "AUTH_UPSTREAM_UNAVAILABLE", ]); const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export async function get( url: string, init: RequestInit, maxAttempts = 4, ): Promise { for (let attempt = 0; attempt < maxAttempts; attempt++) { const res = await fetch(url, init); const body = await res.json(); if ("data" in body) return body.data as T; const { code, message } = body.error; const last = attempt === maxAttempts - 1; if (!RETRYABLE.has(code) || last) throw new Error(`[${code}] ${message}`); const retryAfter = res.headers.get("Retry-After"); const delayMs = retryAfter ? Number(retryAfter) * 1000 : 2 ** attempt * 1000; await sleep(delayMs); } throw new Error("exhausted retries"); } ``` ## MCP surfaces the same errors [#mcp-surfaces-the-same-errors] The MCP tools call the same REST endpoints and fail with the same `code`, `message`, and `retryable`. A tool error renders as a single string: ```text [RATE_LIMITED] Rate limit exceeded. Please slow request rate and retry. (retryable: true) ``` Use the REST envelope and the OpenAPI spec as the contract of record for status codes and shapes. # Freshness (/docs/reference/freshness) Read `freshness` before you display, cache, or summarize a result. It tells you how current the data is and whether the endpoint even applies to this symbol. The five values are a closed set. | Value | Meaning | What an agent does | | ------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `intraday` | A live intraday quote or market-context figure is included. | Use it for current context. Do not present it as exchange-grade real-time or as an execution price. | | `end_of_day` | Last complete end-of-day close, statement, filing, dividend, or historical series. | Use it as the current value for fundamentals, filings, history, and after-hours market context. | | `stale` | A cached fallback older than the normal window was served. | Show it with a "as of `as_of`" caveat; re-request if you need fresher. | | `degraded` | A partial response: one source path was slow, limited, or unavailable. | Use what is present, keep `null`s and reasons, and avoid conclusions that depend on the missing block. | | `unsupported` | The endpoint does not apply to this symbol or asset type. | Show `reason`; do not re-request the same endpoint for the same symbol. | ## `as_of` [#as_of] `as_of` is the timestamp the payload represents, in UTC ISO-8601 (`2026-06-05T14:19:06Z`). * When the source supplies a timestamp, `as_of` is that upstream timestamp. * When it does not, `as_of` is the moment StockContext generated the response. Put `as_of` in any answer or label that asserts a current value, and do not silently swap it for local time: ```text AAPL snapshot as of 2026-06-05T14:19:06Z, freshness: intraday. ``` ## `market_status` [#market_status] `market_status` is `"open"` or `"closed"`, computed from request time against the regular NYSE/NASDAQ session: 09:30–16:00 ET, **Monday through Friday, excluding US market holidays** (about ten per year: New Year's Day, MLK Day, Presidents' Day, Good Friday, Memorial Day, Juneteenth, Independence Day, Labor Day, Thanksgiving, Christmas). A weekday holiday reads `"closed"` even during what would be session hours. It is a session hint, not an exchange operations feed. Do not read halts, auction state, venue status, or execution readiness into it. The stateless lookup routes `/v2/search` and `/v2/coverage` omit it entirely. ## Detecting staleness yourself [#detecting-staleness-yourself] `freshness` is the headline; `as_of` and `cache_age_seconds` are the precise instruments. `cache_age_seconds` is how many seconds the served payload has been cached: `0` means it was computed on this request, and a large value means a cached read ([response model](/docs/reference/response-model) owns this field). To decide whether a number is current enough for your product, compare against `as_of` and `cache_age_seconds` rather than guessing from `freshness` alone: ```python title="staleness.py" from datetime import datetime, timezone def is_stale(data: dict, max_age_seconds: int) -> bool: # Cache age is the direct signal: how long this payload has been held. if data.get("cache_age_seconds", 0) > max_age_seconds: return True # as_of guards against an old upstream timestamp behind a fresh cache. as_of = datetime.fromisoformat(data["as_of"].replace("Z", "+00:00")) age = (datetime.now(timezone.utc) - as_of).total_seconds() return age > max_age_seconds ``` ```ts title="staleness.ts" function isStale(data: { as_of: string; cache_age_seconds?: number; }, maxAgeSeconds: number): boolean { if ((data.cache_age_seconds ?? 0) > maxAgeSeconds) return true; const ageSeconds = (Date.now() - Date.parse(data.as_of)) / 1000; return ageSeconds > maxAgeSeconds; } ``` Pick `max_age_seconds` by data family: seconds-to-minutes for an intraday quote, hours for fundamentals (a real `/v2/fundamentals` read returned `cache_age_seconds: 16096`, about four and a half hours, which is normal, since statements do not move intraday). ## Unsupported is a 200, not an error [#unsupported-is-a-200-not-an-error] When an endpoint does not apply, you get HTTP 200 with `freshness: "unsupported"` and a machine-readable `reason`. This is a live `/v2/valuation` for SPY: ```json title="GET /v2/valuation?symbol=SPY, HTTP 200" { "data": { "symbol": "SPY", "asset_type": "etf", "freshness": "unsupported", "reason": "etf_no_valuation_multiples", "as_of": "2026-06-05T14:19:10Z", "market_status": "open", "cache_age_seconds": 0 } } ``` The reasons are stable strings you can branch on: `etf_no_valuation_multiples`, `symbol_does_not_file_us_domestic_forms`, `not_applicable_for_etf`. Retrying will not change the answer; which families return `unsupported` for which assets is documented in [coverage and gaps](/docs/reference/coverage-and-gaps). ## When to re-request [#when-to-re-request] `freshness` controls retry behavior for successful responses; `error.retryable` controls it for failures. * `stale`: re-request only if your product needs fresher data right now. * `degraded`: re-request only when the missing block is the one you need. * `unsupported`: do not re-request without changing the endpoint or the symbol. * For `RATE_LIMITED`, `UPSTREAM_UNAVAILABLE`, and `AUTH_UPSTREAM_UNAVAILABLE`, follow `error.retryable` and `Retry-After` per the [error codes](/docs/reference/error-codes). # Plans and limits (/docs/reference/plans-and-limits) A plan sets three things for a key: which routes it can call, how many requests per minute it gets, and its quota over a longer window. The data on every route is identical across plans: paid plans buy throughput, not different numbers. | Plan | Per minute | Quota | Routes | | ------- | ---------: | -----------: | --------------------------------------------- | | Free | 30/min | 100/day | `search`, `coverage`, `snapshot`, `valuation` | | Starter | 60/min | 10,000/month | all routes | | Builder | 300/min | 75,000/month | all routes | Prices are at [/pricing](/pricing). Pick Builder over Starter when you need the higher ceilings (300/min and 75,000/month against 60/min and 10,000) for fan-out, batch jobs, or bursty agent traffic. The responses are byte-for-byte the same. ## Route access [#route-access] Free covers the proof loop: discover a symbol with `/v2/search`, confirm what it supports with `/v2/coverage`, pull a `/v2/snapshot`, and run a `/v2/valuation`. Every other route requires Starter or Builder. A Free key that calls a paid route gets HTTP `403` with `PLAN_UPGRADE_REQUIRED`: ```json { "error": { "code": "PLAN_UPGRADE_REQUIRED", "message": "This endpoint requires Starter or Builder. Free includes search, coverage, snapshot, and valuation.", "retryable": false } } ``` This is not retryable. Upgrade the key, or call one of the four Free routes. ## Rate-limit headers [#rate-limit-headers] Every response that passes key verification carries three headers describing your current minute bucket, successes and post-auth errors alike (`400`, `403`, `404 SYMBOL_UNKNOWN`, `429`). Only a pre-auth `401` or an unmatched-route `404 NOT_FOUND` omits them, since both fail before the key is checked: ```text X-RateLimit-Limit: 60 X-RateLimit-Remaining: 59 X-RateLimit-Reset: 1798912312 ``` | Header | Meaning | | ----------------------- | ---------------------------------------------- | | `X-RateLimit-Limit` | Requests allowed in the current minute window. | | `X-RateLimit-Remaining` | Requests left in that window. | | `X-RateLimit-Reset` | Unix epoch seconds when the window resets. | Read `X-RateLimit-Remaining` to pace yourself before you ever hit a `429`. ## Hitting a ceiling [#hitting-a-ceiling] When you exceed the per-minute rate or exhaust your quota, requests return HTTP `429` with `RATE_LIMITED` until the window resets: the **daily** window for Free, the **monthly** window for Starter and Builder. A `429` adds a `Retry-After` header (seconds) and sets `X-RateLimit-Remaining` to `0`. The headers on a `429` describe **whichever window tripped**. A minute-bucket 429 shows your per-minute limit and a `Retry-After` of seconds. A quota 429 flips `X-RateLimit-Limit` to the quota itself (100 on Free), sets `X-RateLimit-Reset` to the window's end, and sends a `Retry-After` of hours: a Free daily 429 reports the seconds until midnight UTC. There is no header that counts the daily or monthly budget down during normal operation; if quota matters to your job, count requests yourself. This is a minute-bucket 429: ```http title="429 captured 2026-06-05" HTTP/1.1 429 Too Many Requests X-RateLimit-Limit: 30 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1780669200 Retry-After: 21 { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please slow request rate and retry.", "retryable": true } } ``` `RATE_LIMITED` is retryable. Stop sending for that key, wait `Retry-After` (21 seconds in this capture) or until `X-RateLimit-Reset`, then resume at lower concurrency and cache repeat reads. The same gating applies to the hosted MCP tools, since they call the same routes. For retry mechanics across every error, see [error codes](/docs/reference/error-codes). # Response model (/docs/reference/response-model) Every StockContext REST response is one of two top-level shapes. Branch on which key is present, not on the endpoint and not on the HTTP status. | Outcome | Top-level shape | | ------- | ------------------------------------------------- | | Success | `{ "data": ... }` | | Failure | `{ "error": { "code", "message", "retryable" } }` | There is never a raw endpoint object, a bare array, or an upstream provider payload at the top level. The MCP tools return the identical envelope, so a parser written against this page works for both surfaces. ## Success envelope [#success-envelope] Success is exactly a top-level `data` object. Here is a real `/v2/snapshot` response for AAPL, trimmed to the wrapper and the common fields; the live response also carries `quote`, `performance`, `range_52w`, and `fundamentals_quick` blocks. ```json title="GET /v2/snapshot?symbol=AAPL" { "data": { "symbol": "AAPL", "name": "Apple Inc", "currency": "USD", "as_of": "2026-06-05T14:19:06Z", "freshness": "intraday", "market_status": "open", "cache_age_seconds": 0, "asset_type": "stock" } } ``` The object inside `data` is endpoint-specific. The wrapper is not. ## Error envelope [#error-envelope] Failure is exactly a top-level `error` object with three fields. This is a live 429 from `/v2/snapshot`: ```json title="GET /v2/snapshot?symbol=AAPL, HTTP 429" { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded. Please slow request rate and retry.", "retryable": true } } ``` `error.retryable` is the retry contract. Retry only when it is `true`, and still back off: see [error codes](/docs/reference/error-codes) for the full table and the worked retry loop. On a 429 the response also carries rate-limit headers; this example sent `Retry-After: 21` and `X-RateLimit-Remaining: 0`. ## Common fields on `data` [#common-fields-on-data] Most symbol endpoints put these fields at the top of `data`. The exact set varies by endpoint, but when a field appears it means the same thing everywhere. | Field | Meaning | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `symbol` | The resolved symbol for endpoints that take one (normalized, e.g. `BRK-B`). | | `as_of` | The timestamp the payload represents: upstream timestamp when the source gives one, otherwise generation time. UTC ISO-8601. See [freshness](/docs/reference/freshness). | | `freshness` | One of `intraday`, `end_of_day`, `stale`, `degraded`, `unsupported`. Owned by [freshness](/docs/reference/freshness). | | `market_status` | `open` or `closed`. Omitted on stateless lookup routes (`/v2/search`, `/v2/coverage`). Owned by [freshness](/docs/reference/freshness). | | `cache_age_seconds` | Seconds the served data has been cached. `0` means freshly computed; a large value means a cached read. Pair it with `as_of` to judge staleness. | | `currency` | Present only when the payload carries monetary values; `USD` across the supported universe. | | `asset_type` | `stock` or `etf`. Present where the two shapes branch. | | `period` | Present on period-based endpoints (`/v2/fundamentals`, `/v2/history`). Units owned by [units and fields](/docs/reference/units-and-fields). | `cache_age_seconds` is the field to read when you care whether a number is live. In the snapshot above it is `0` (computed on request); a `/v2/fundamentals` read for the same symbol returned `16096`, meaning the statement model was served from a cache roughly four and a half hours old. That is expected for fundamentals, since statements do not change intraday, but it is the value to surface if your product needs to reason about how current a figure is. ## Unsupported is a success, not an error [#unsupported-is-a-success-not-an-error] When an endpoint does not apply to a symbol, you get HTTP 200 with a normal `data` object whose `freshness` is `"unsupported"` and whose `reason` is a machine-readable string. This is a live `/v2/valuation` for SPY, where ETFs have no valuation multiples: ```json title="GET /v2/valuation?symbol=SPY, HTTP 200" { "data": { "symbol": "SPY", "asset_type": "etf", "freshness": "unsupported", "reason": "etf_no_valuation_multiples", "as_of": "2026-06-05T14:19:10Z", "market_status": "open", "cache_age_seconds": 0 } } ``` Do not treat this as an outage and do not retry it for the same symbol; the answer will not change. Show the `reason` and move on. [Coverage and gaps](/docs/reference/coverage-and-gaps) lists which families return `unsupported` for which assets. ## Three ways a value can be absent [#three-ways-a-value-can-be-absent] Inside a `data` object, a value can be missing in three distinct ways, and they mean different things. This is the table other pages link to; do not re-derive it elsewhere. | Pattern | What it looks like | Meaning | What to do | | -------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `null` | `"field": null` | The field is meaningful here, but no value is available for this row or symbol. | Render "not available" or omit the cell. Do not compute with it. | | Omitted key | the key is simply not present | A persistent source gap, or an optional field that does not apply. | Treat as absent. Do not invent it. | | Unavailable envelope | `"field": { "available": false, "reason": "..." }` | The value is intentionally suppressed, and `reason` says why. | Keep the `reason` if you display the field. Do not retry to "get the number." | The unavailable envelope is common and load-bearing. It replaces a scalar in place: wherever a number would sit, you may instead find `{ "available": false, "reason": ... }`. A real example from `/v2/insider`, where a gift has no market price: ```json { "transaction_kind": "gift", "shares": 65000, "price_per_share": { "available": false, "reason": "gift_no_market_price" }, "value_usd": { "available": false, "reason": "gift_no_market_price" } } ``` Parse defensively: before doing math on a field that is normally a number, check that it is one. Financial-statement guardrails use the same shape: for example `interest_coverage` becomes `{ "available": false, "reason": "zero_interest_expense" }` rather than dividing by zero. ## Parse by envelope, branching on retryable [#parse-by-envelope-branching-on-retryable] A correct client reads `data` versus `error` first, and on error keeps `code` and `retryable` so the caller can decide whether to retry. The two parsers below do exactly that. ```python title="parse.py" import httpx class NoxstockError(Exception): def __init__(self, code: str, message: str, retryable: bool, status: int): super().__init__(f"[{code}] {message}") self.code = code self.message = message self.retryable = retryable self.status = status def parse(response: httpx.Response) -> dict: body = response.json() if "error" in body: err = body["error"] raise NoxstockError( err["code"], err["message"], err["retryable"], response.status_code ) return body["data"] ``` ```ts title="parse.ts" type Envelope = | { data: T } | { error: { code: string; message: string; retryable: boolean } }; export class NoxstockError extends Error { constructor( readonly code: string, message: string, readonly retryable: boolean, readonly status: number, ) { super(`[${code}] ${message}`); } } export async function parse(response: Response): Promise { const body = (await response.json()) as Envelope; if ("error" in body) { const { code, message, retryable } = body.error; throw new NoxstockError(code, message, retryable, response.status); } return body.data; } ``` The caller catches `NoxstockError` and checks `retryable` before deciding to retry. The full backoff loop that honors `Retry-After` lives with the [error codes](/docs/reference/error-codes). Every code, its HTTP status, whether it is retryable, and what to do about it. # SEC analyst core catalog (/docs/reference/sec-data-catalog) StockContext's public claim is narrow and checkable: **analyst-useful SEC facts for active US-listed companies, cited to the accession, fresh in minutes on the filing-driven path where applicable, and explicit reasons when a fact cannot be served.** This page is that claim, enumerated. Two things make the catalog different from a source list. Every served value carries **per-value provenance** (the SEC accession, form, and filing date it came from — `provenance=full` on `/v2/facts` adds the exact XBRL tag), and every boundary is **labeled in the payload itself**: coverage eras, publication lags, and refusals are machine-readable fields, never footnotes. When a parse is uncertain, the API refuses with a reason instead of serving a guess. ## What is collected and where it serves [#what-is-collected-and-where-it-serves] | Dataset | Serves on | Verified by | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Financial statements + per-value provenance, point-in-time history, restatement flags, IFRS/20-F, multi-class EPS | `/v2/fundamentals`, `/v2/facts`, `/v2/history` | nightly coherence referee (G-COHERE), external anchors vs re-parsed filings, golden values | | Generic fact catalog (\~150 concepts), as-known-on-date queries | `/v2/facts` (`as_of`, `provenance=full`) | G-PIT property gate; registry pinned to the engine catalog in CI | | Filings, section text, section diffs | `/v2/filings` (+ subresources) | citation gate (G-CITE) | | Classified events: 8-K items, late filings, delisting, deregistration, offerings/dilution, M\&A/tender/going-private, comment letters, going-concern detections, 5% stakes | `/v2/events` (frozen label vocabulary, `labels=` filter) | classification goldens — every label pinned to ≥3 real filings in CI | | Insider Form 4 transactions + distinct-buyer clusters + 10b5-1 flags + Form 144 intent-to-sell notices (Forms 3/5 are source-available future collection, not currently served) | `/v2/insider` | goldens cross-checked against raw filing XML | | Company-stated guidance (the estimates substitute) | `/v2/earnings.company_guidance` | serve-or-refuse anchoring (every served number's period/metric/unit/basis is text-anchored); 20-filing goldens; nightly parse-rate drift gate (G-GUID) | | Auditor identity (name, PCAOB ID, location), filer category, public float | `/v2/profile` | three hand-verified covers; float equal to the dollar vs the companyfacts API | | ETF holdings (N-PORT, top-10) for the 57-ETF tier | `/v2/profile` (ETF variant) | three-fund goldens with offline replay (G-NPORT) | | Fails-to-deliver (semi-monthly CNS) | `/v2/price-action?blocks=fails_to_deliver` | per-file row-count + date-window integrity; cycle-staleness refusal | Freshness is measured, not claimed: issuer-keyed filings flow filing → served in minutes (per-form receipts are published on the ops surface), and everything slower says so in its own payload — 13F carries `as_of_quarter_end` + `filed_through`, N-PORT carries `as_of` with its \~60-day publication lag, fails-to-deliver carries `as_of_settlement_date` + `data_lag_days`. ## The estimates question [#the-estimates-question] There is no analyst consensus here, ever — consensus data exists only inside commercial aggregators (LSEG, FactSet, Zacks) under licenses that are a legal trap for agent products that redistribute outputs. The substitutes are structurally better for agents and 100% redistributable: * **what the company itself guided** (`company_guidance`, extracted under a strict serve-or-refuse rule), * **what the market price implies** (`/v2/priced-in` reverse-DCF), and * **what insiders and SEC event filings disclose** (`/v2/insider` and `/v2/events`). ## Private beta, hidden from public core [#private-beta-hidden-from-public-core] These families remain internally callable for explicitly enabled integrations, but are not in public MCP tools, endpoint chooser guidance, or default agent recipes until coverage gates are strong enough: | Family | Why it is private beta | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Segments | Valuable for business-line analysis, but the coverage gate must distinguish issuer disclosure reality, taxonomy variation, and recast history before public-core positioning. | | Ownership / 13F / 13D-G | Valuable for institutional and 5% holder analysis, but quarterly lag, class-level security matching, and completeness gates need more production sampling before public-core positioning. | | Governance / proxy facts | Valuable for compensation and voting analysis, but proxy taxonomy coverage and collection completeness are still being validated. | ## What is excluded, and why (the honest perimeter) [#what-is-excluded-and-why-the-honest-perimeter] | Excluded | Written reason | | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | Analyst estimates / consensus / surprise-vs-consensus | Licensed-only data; the razor (facts, math, provenance — never opinions). See the substitutes above. | | News | Intentionally absent forever — narrative is not a fact an agent can cite. | | Delisted companies, OTC, pre-2009 backfill | Owner scope decision: active US-exchange-listed only. | | Fund proxy voting (N-PX) | Fund-side data; issuer-side vote outcomes already serve via 8-K Item 5.07 events. | | SEC enforcement / litigation releases | Not EDGAR; respondent→ticker matching is error-prone and fails refuse-on-uncertain economics. | | Exchange short interest | FINRA/exchange licensed data, not SEC. Fails-to-deliver is the SEC-side cousin and is served. | | CEO/CFO certifications (EX-31/32) | Boilerplate; the signal cases surface via late-filing, 4.02, and auditor events. | | Proxy text beyond the ecd taxonomy (director bios, pre-2022 comp) | No structured taxonomy exists — extraction would be scrape-grade, below the precision bar. Competitors' deeper comp history is scrape-sourced. | | Form D / Reg A / Form C / ADV / N-CEN / N-MFP | Private-company / adviser / fund-ops surfaces outside issuer scope. | | SPY's N-PORT holdings | Not a gap: SPY is a unit investment trust, which the SEC does not require to file N-PORT. The payload says `fund_files_no_nport`. | Known boundaries inside public-core families are labeled in the payloads: auditor tags start FY2021; going-concern detection runs from 2026-06-12 forward (historical backfill queued); guidance covers the trailing \~120 days plus everything filed since 2026-06-12. # Units and fields (/docs/reference/units-and-fields) Field names carry their units in the suffix, so the number itself is unambiguous once you know the suffix. Store and compute on the raw value; only convert at the display edge, and label the conversion when you do. `dividend_yield_pct` is consistent across `snapshot`, `valuation`, `dividends`, and `compare`: it is the annual dividend rate divided by the latest price (AAPL reports `0.35` on all four). The rate is forward-annualized when the payer's cadence is detectable and trailing-twelve-month for irregular payers; `/v2/dividends` labels which convention you got via `current.yield_basis`. Only last-digit rounding can ever differ; a larger gap means you compared `as_of` timestamps that span a price move, not a data problem. ## Unit suffixes [#unit-suffixes] These are the suffixes you will see across the API, each verified against live fixtures. | Suffix | Meaning | Example from a live response | | ------------------ | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | `_usd` | Whole US dollars, not thousands, not millions. | `market_cap_usd: 4512101567600` is $4.512T; `value_usd: 15551000.0` is $15.55M. | | `_pct` | Percentage points, already scaled, not a 0–1 fraction. | `dividend_yield_pct: 0.35` means 0.35%; `fcf_margin_pct: 24.04` means 24.04%. | | `_seconds` | A duration in seconds. | `cache_age_seconds: 16096`. | | share-count fields | Whole share counts. | `shares_outstanding: 14725873000`, plus `shares_basic`, `shares_diluted`, `shares_outstanding_cover_page`. | | no suffix | A dimensionless ratio or multiple. | `current_ratio: 1.07`, `debt_to_equity: 0.8`, `beta_5y_weekly: 1.17`. | A few bare fields are dollars by convention rather than suffix: line items inside a statement block (`revenue`, `operating_cash_flow`, `capex`) and `price_per_share`. The enclosing object carries `"currency": "USD"`; read it rather than assuming. The headline multiples `pe_ttm`, `ps_ttm`, `pb`, and `ev_ebitda_ttm` are the exception: on `/v2/snapshot` and `/v2/valuation` they ship as objects, not bare numbers. The scalar lives at `.value` (snapshot AAPL `pe_ttm` is `{ "value": 36.81, "vs_5y": "near_5y_high" }`; valuation wraps it as `{ value, vs_own_history, change? }`). `/v2/compare` unwraps them to bare floats. A negative P/E still binds at `pe_ttm.value`, but carries a caveat such as `"negative_earnings_period"` and compare adds `pe_ttm_negative_not_meaningful`. Read `pe_ttm.value` on snapshot/valuation; never multiply the object itself. ### The `_pct` rule, worked [#the-_pct-rule-worked] `_pct` values are percentage points. To use one as a multiplier, divide by 100 first. ```python yield_pct = 0.35 # from dividend_yield_pct annual_income = price * (yield_pct / 100) # NOT price * 0.35 ``` Treating `dividend_yield_pct: 0.35` as the fraction `0.35` overstates the figure by 100x. This is the single most common unit mistake; the divide-by-100 is on you, not the API. ## Signs are meaningful: never flip them [#signs-are-meaningful-never-flip-them] A negative number is information. Preserve the sign through your pipeline. ```json { "drawdown_from_high_pct": -1.8, "working_capital_change": -14772000000, "net_value_usd": -111705105.08 } ``` `drawdown_from_high_pct: -1.8` says the price sits 1.8% below its 52-week high: the negative is the drawdown. A negative `working_capital_change` is a normal cash-flow movement, not an error. A negative `net_value_usd` on an insider rollup signals net selling. Stripping the minus sign to make a value "look clean" changes its meaning. ## Timestamps and dates [#timestamps-and-dates] Point-in-time timestamps are UTC ISO-8601 with a `Z`: ```json "as_of": "2026-06-05T14:19:06Z" ``` Date-only fields use `YYYY-MM-DD`: ```json "period_end": "2026-03-28" ``` Keep the two kinds separate. `as_of` timestamps the payload and its source context ([freshness](/docs/reference/freshness) owns its semantics); `period_end`, `filing_date`, `transaction_date`, and event `date` are business or filing dates, not data-currency signals. ## The `/v2/history` columnar shape [#the-v2history-columnar-shape] `/v2/history` returns a columnar table: a `columns` array naming each position, and `rows` as compact arrays in that exact order. Read `columns` first, then index `rows` by position; never assume a fixed layout, because the columns follow your `series` request. Three fields tie a history response together: * `period`: the lookback window you asked for (`1m`, `1y`, `5y`, …); the request parameter is `range`, and `period` echoes it back. * `cadence`: the row spacing: `daily`, `weekly`, `quarterly`, `annual`, or `per-payment`. * `columns`: the order of values inside every row. This is a live `/v2/history?symbol=AAPL&series=close,volume&range=1m`, trimmed to the first three of its daily rows (the live response returns the full month): ```json title="GET /v2/history?symbol=AAPL&series=close,volume&range=1m" { "data": { "symbol": "AAPL", "period": "1m", "cadence": "daily", "as_of": "2026-06-05T14:19:11Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 0, "columns": ["date", "close", "volume"], "rows": [ ["2026-05-06", 287.25, 58336072], ["2026-05-07", 287.18, 45224300], ["2026-05-08", 293.05, 52692761] ], "coverage": { "start": "2026-05-06", "end": "2026-06-05" } } } ``` `columns` is `["date", "close", "volume"]`, so `row[0]` is the date, `row[1]` the close, and `row[2]` the volume. Request more series and both `columns` and each row grow in lockstep: `series=close,volume,rsi_14` yields `["date", "close", "volume", "rsi_14"]` and four-element rows. `/v2/history` is always `freshness: "end_of_day"` (never intraday); it also returns `market_status`, `cache_age_seconds`, and a `coverage: {start, end}` object marking the actual span the response covers. Price values are rounded to two decimal places and `volume` is an integer, so no further rounding is needed. ## How a few cross-cutting metrics are computed [#how-a-few-cross-cutting-metrics-are-computed] Several numbers carry a methodology that is not obvious from the suffix. Read these before labeling them in a summary: * `dividend_yield_pct`: the annual dividend rate divided by the latest price. The rate is **forward-annualized** for payers with a detectable cadence and falls back to **trailing-twelve-month** for irregular payers; `/v2/dividends` tells you which one you got via `current.yield_basis` (`"forward_annualized"` or `"trailing_12m"`), always present when `yield_pct` is numeric. Whichever the basis, the value is identical across `snapshot`, `valuation`, `dividends`, and `compare`. * `volume.latest` (price action): the most recent session's consolidated volume; `avg_30d` is the trailing 30-session average, and `ratio_to_avg` is `latest / avg_30d`. `ratio_to_sector_avg` divides the latest session's volume by the sector benchmark ETF's average daily volume; it is value-or-envelope with the same shape and reasons as `vs_sector_pct_1y` below. * `vs_sector_pct_1y` (on `snapshot.performance` and `technicals.relative_strength`): the symbol's 1-year return minus its sector benchmark ETF's 1-year return, in percentage points. The field is value-or-envelope: a number, or `{ "available": false, "reason": "not_applicable_for_etf" | "sector_benchmark_unavailable" }` — the reason rides inside the field itself; there is no sibling `vs_sector_status` object. A bare `null` means exactly one thing: the symbol lacks enough trading history for the window. * `beta_5y_weekly` (on `snapshot` and `compare`): beta versus the market (SPY) from **weekly** returns over the trailing \~5 years — the stable systematic-risk estimate (use it for WACC / cost-of-equity). An `{ available: false, reason }` object when it cannot be computed or comes out implausibly negative. * `beta_1y_weekly` (on `technicals.volatility`): beta versus the market (SPY) from **weekly** returns over the trailing \~1 year. It is a **different number** from `beta_5y_weekly` — the shorter window reacts faster to a recent change in volatility, so the two can diverge materially. Pick the window you actually need; never mix them. ## Absent values [#absent-values] A value can be `null`, an omitted key, or an `{ "available": false, "reason": ... }` envelope, and the three mean different things. That table is owned by the [response model](/docs/reference/response-model#three-ways-a-value-can-be-absent): check there before writing absence-handling code, and do not invent a field that the response did not return. # Hosted MCP quickstart (/docs/mcp/hosted) Connect a client to `https://api.stockcontext.com/mcp` and the 21 public `stockcontext_*` tools load. Auth is the same `X-API-Key` header REST uses. This page gets you from zero to a verified `stockcontext_snapshot` call. Keep the key in your client config, shell env, or a secret manager. Do not paste a full key into chat, logs, screenshots, or committed config. ## Get a key [#get-a-key] Create one at [stockcontext.com/dashboard/keys](https://stockcontext.com/dashboard/keys). The raw secret (prefix `sctx_`) is shown once at creation. Export it so the client config below can reference it: ```bash export STOCKCONTEXT_API_KEY="sctx_your_key_here" ``` ## Add the server [#add-the-server] In Claude Code, register the hosted server over HTTP: ```bash claude mcp add --transport http StockContext https://api.stockcontext.com/mcp \ --header "X-API-Key: $STOCKCONTEXT_API_KEY" ``` For a client that reads JSON config (Cursor, for example), add the server to its MCP config: ```json title="mcp.json" { "mcpServers": { "StockContext": { "url": "https://api.stockcontext.com/mcp", "headers": { "X-API-Key": "sctx_your_key_here" } } } } ``` ## Smoke test [#smoke-test] Confirm the tools loaded, then call `stockcontext_snapshot` for AAPL: ```json { "tool": "stockcontext_snapshot", "arguments": { "symbol": "AAPL" } } ``` You get the REST `{ "data": ... }` envelope unchanged, as text JSON, structured content, or both, depending on the client: ```json title="stockcontext_snapshot AAPL (trimmed)" { "data": { "symbol": "AAPL", "name": "Apple Inc", "currency": "USD", "as_of": "2026-06-05T20:00:00Z", "freshness": "end_of_day", "market_status": "closed", "cache_age_seconds": 2, "quote": { "price": 307.34, "change_pct": -1.25, "previous_close": 311.23, "volume": 65310502 }, "asset_type": "stock", "sector": "Technology", "industry": "Consumer Electronics" } } ``` Every success envelope carries `freshness`, `as_of`, `market_status`, and `cache_age_seconds` (int; `0` when freshly computed). Trimmed here; the live response also returns `performance`, `range_52w`, and a `fundamentals_quick` block where each headline multiple (`pe_ttm`, `ps_ttm`, `pb`, `ev_ebitda_ttm`) is a `{value, vs_5y}` object, not a bare float. See [snapshot](/docs/api-reference/endpoints/core-context/snapshot) for the full payload. ## What failure looks like [#what-failure-looks-like] A REST error comes back in the `{ "error": ... }` envelope, which most clients render as a tool-error string `[CODE] message (retryable: ...)`. Two you should expect: ```text [UNAUTHORIZED] Missing or invalid X-API-Key header. (retryable: false) [PLAN_UPGRADE_REQUIRED] This endpoint requires Starter or Builder. Free includes search, coverage, snapshot, and valuation. (retryable: false) ``` `PLAN_UPGRADE_REQUIRED` is an access result, not an outage. The full set of codes lives in [error codes](/docs/reference/error-codes). ## Troubleshooting [#troubleshooting] Two failure modes that look similar but have different causes: * **No tools load at all.** The client never reached or never authenticated with the server. Check the URL is exactly `https://api.stockcontext.com/mcp`, the transport is HTTP (not stdio), and the `X-API-Key` header is actually being sent. At the wire, a missing or invalid key is rejected during the handshake with a plain HTTP `401` carrying the REST error envelope, and most clients surface that as "no tools" or a connection error, not as a tool failure. * **Tools load but a call errors.** The connection and key work; the plan or arguments do not. Tool failures render as the bracket string: `[PLAN_UPGRADE_REQUIRED] This endpoint requires Starter or Builder. … (retryable: false)` means a Free key called a paid tool. On Free, only `stockcontext_search`, `stockcontext_coverage`, `stockcontext_snapshot`, and `stockcontext_valuation` are available. See [plans and limits](/docs/reference/plans-and-limits) for what each plan includes. ## Your first cited analysis — under 5 minutes, bill included [#your-first-cited-analysis--under-5-minutes-bill-included] Everything above (key → config → smoke test) takes a few minutes of human time; what follows takes the agent seconds. One `stockcontext_snapshot` call gives an agent everything below — every number dated, every filing citable by SEC accession: ```text As of 2026-06-11T20:00:00Z (freshness: end_of_day): - Price $295.63 (+1.39% on the session) - P/E (TTM) 35.79 — near_5y_high, z +1.3 vs its OWN 5-year history - Latest disclosure: 10-Q filed 2026-05-01 — accession 0000320193-26-000013 - Next earnings (estimated from the issuer's own filing cadence): 2026-07-31 Token bill: 565 tokens (o200k_base) for the entire tool result. ``` That 565-token bill is the point: response tokens are your downstream model bill, and StockContext payloads are designed as prompts — small by default, depth opt-in (`blocks=`, `fields=`). The equivalent statement payload from a typical financial-data API runs thousands of tokens per call. The timing-and-bill check above is reproducible from the repo's committed script — `scripts/check_quickstart_timing.py` plays the exact MCP handshake → tool-call → cited-facts path and fails if the machine portion ever exceeds its budget. ## Custom clients [#custom-clients] Writing your own client instead of using a turnkey one? The server speaks standard MCP over Streamable HTTP: POST JSON-RPC to `https://api.stockcontext.com/mcp` with your `X-API-Key` header plus `Accept: application/json, text/event-stream`, run the usual `initialize` → `notifications/initialized` handshake, then `tools/list` and `tools/call`. Nothing here is StockContext-specific: any spec-compliant MCP client implementation works unchanged. All 21 public tools, their REST routes, and argument conventions. # MCP overview (/docs/mcp/overview) StockContext is MCP-native. 21 public `stockcontext_*` tools cover the analyst core, and each one calls a REST route and returns the exact same envelope, so an agent gets typed market context (snapshots, valuation, fundamentals, filings, insider activity, events, and price history) without you writing an API client. Every route is a GET, safe to call in any agent loop. The context is actuals-only and ranked against each symbol's own history: no analyst consensus, news, or peer/sector medians. Free keys can call `stockcontext_search`, `stockcontext_coverage`, `stockcontext_snapshot`, and `stockcontext_valuation`. The other 17 public tools need Starter or Builder; a Free key calling them returns `403 PLAN_UPGRADE_REQUIRED`. Point a client at the hosted server and the tools appear: ```bash claude mcp add --transport http StockContext https://api.stockcontext.com/mcp \ --header "X-API-Key: $STOCKCONTEXT_API_KEY" ``` That one line gets you connected. The [hosted quickstart](/docs/mcp/hosted) covers keys, a second client config, the smoke test, and what failures look like. When a client display and the wire disagree, REST and the [OpenAPI schema](/docs/api-reference/overview) are the contract of record. MCP tools return REST envelopes unchanged: they never reshape them. Get a key, add the server, run the smoke test. Start here. All 21 public tools, their REST routes, plan access, and argument conventions. Built-in prompt templates and JSON reference resources. # Prompts and resources (/docs/mcp/prompts-and-resources) The server ships four read-only resources and four prompts alongside the [tools](/docs/mcp/tools). ## Resources [#resources] Resources are JSON reference documents an agent can read without spending a tool call or a plan quota. | Resource | Returns | | ------------------------------------------- | ---------------------------------------------------- | | `stockcontext://reference/error-codes` | Error codes with their `retryable` flag and meaning. | | `stockcontext://reference/sectors` | GICS sector to SPDR sector-ETF reference. | | `stockcontext://reference/openapi` | The REST OpenAPI schema. | | `stockcontext://coverage/supported-tickers` | Every supported stock and ETF ticker. | From an MCP client, list what is available and read one by URI: ```json { "method": "resources/list" } ``` ```json { "method": "resources/read", "params": { "uri": "stockcontext://reference/error-codes" } } ``` Resources are best-effort. The `stockcontext://reference/openapi` resource embeds an `{ "error": ... }` object if the upstream schema fetch fails, rather than failing the read. ## Prompts [#prompts] | Prompt | Purpose | | ------------------------- | --------------------------------------- | | `decision_brief(symbol)` | Single-symbol context brief. | | `earnings_risk(symbol)` | Earnings setup and risk for one symbol. | | `insider_scan(symbol)` | Form 4 activity review for one symbol. | | `compare_basket(symbols)` | 2–12 symbol comparison. | Prompts are client-side templates. They tell the agent which tools to call in what order; they do not run a server-side workflow. Invoke one the way your client invokes prompts: ```json { "method": "prompts/get", "params": { "name": "decision_brief", "arguments": { "symbol": "AAPL" } } } ``` The client gets back the template text and the agent calls the tools it names. The templates also carry the read guardrails: valuation percentiles and labels (`near_5y_low`, etc.) rank a multiple against the company's own history only — never peers, sector, or fair value — so a low rank is not a cheapness verdict, and earnings guidance excerpts are source text, not parsed consensus. For the worked-out version of each flow, see [agent recipes](/docs/agents/recipes). # MCP tools (/docs/mcp/tools) Each tool wraps one REST route and returns its envelope unchanged. The endpoint page for each route documents the full parameters and response; the columns below are the map. | Tool | REST route | Plan | Docs | | ------------------------------ | -------------------------------------------- | ---- | -------------------------------------------------------------------------- | | `stockcontext_search` | `GET /v2/search` | Free | [search](/docs/api-reference/endpoints/discovery/search) | | `stockcontext_coverage` | `GET /v2/coverage` | Free | [coverage](/docs/api-reference/endpoints/discovery/coverage) | | `stockcontext_snapshot` | `GET /v2/snapshot` | Free | [snapshot](/docs/api-reference/endpoints/core-context/snapshot) | | `stockcontext_valuation` | `GET /v2/valuation` | Free | [valuation](/docs/api-reference/endpoints/core-context/valuation) | | `stockcontext_priced_in` | `GET /v2/priced-in` | Paid | [priced-in](/docs/api-reference/endpoints/core-context/priced-in) | | `stockcontext_compare` | `GET /v2/compare` | Paid | [compare](/docs/api-reference/endpoints/core-context/compare) | | `stockcontext_profile` | `GET /v2/profile` | Paid | [profile](/docs/api-reference/endpoints/company-context/profile) | | `stockcontext_fundamentals` | `GET /v2/fundamentals` | Paid | [fundamentals](/docs/api-reference/endpoints/financials/fundamentals) | | `stockcontext_earnings` | `GET /v2/earnings` | Paid | [earnings](/docs/api-reference/endpoints/financials/earnings) | | `stockcontext_dividends` | `GET /v2/dividends` | Paid | [dividends](/docs/api-reference/endpoints/financials/dividends) | | `stockcontext_technicals` | `GET /v2/technicals` | Paid | [technicals](/docs/api-reference/endpoints/market-context/technicals) | | `stockcontext_price_action` | `GET /v2/price-action` | Paid | [price-action](/docs/api-reference/endpoints/market-context/price-action) | | `stockcontext_history` | `GET /v2/history` | Paid | [history](/docs/api-reference/endpoints/market-context/history) | | `stockcontext_calendar` | `GET /v2/calendar` | Paid | [calendar](/docs/api-reference/endpoints/events/calendar) | | `stockcontext_filings_list` | `GET /v2/filings` | Paid | [filings list](/docs/api-reference/endpoints/sec-filings/filings-list) | | `stockcontext_filings_get` | `GET /v2/filings/{accession}` | Paid | [filing](/docs/api-reference/endpoints/sec-filings/filing) | | `stockcontext_filings_section` | `GET /v2/filings/{accession}/section/{name}` | Paid | [filing section](/docs/api-reference/endpoints/sec-filings/filing-section) | | `stockcontext_insider` | `GET /v2/insider` | Paid | [insider](/docs/api-reference/endpoints/sec-filings/insider) | | `stockcontext_events` | `GET /v2/events` | Paid | events | | `stockcontext_facts` | `GET /v2/facts` | Paid | facts | | `stockcontext_filing_diff` | `GET /v2/filings/{accession}/diff` | Paid | filing diff | Free tools are `search`, `coverage`, `snapshot`, and `valuation`. Every `Paid` tool needs a paid plan — Starter or Builder both satisfy it. For per-plan rate limits and quotas, see [plans and limits](/docs/reference/plans-and-limits). Segments, ownership/13F/13D-G, and governance are private beta: their REST routes remain available only for explicitly enabled integrations, but they are not part of the default public MCP tool surface. ## Argument conventions [#argument-conventions] Tool arguments are native JSON, not query strings. Three rules cover everything: * **Lists are arrays.** `symbols` for compare is `["AAPL", "NVDA"]`; `forms` for filings is `["10-K", "10-Q"]`; `series` for history is a list. Do not pass comma-separated strings. The arrays have bounds: compare takes 2–12 unique `symbols` and up to 24 `fields`, filings takes up to 4 `forms`, and history takes up to 3 `series` on a single `cadence`. * **The history window argument is named `period`.** It maps to the REST `range` parameter (whose own legacy alias is also `period`), so values like `1y`, `5y`, `ytd`, and `max` apply. History also takes `cadence` (`daily`, `weekly`, `quarterly`, `annual`, `per-payment`), and `series: ["dividend"]` requires `cadence: "per-payment"` explicitly. * **Everything else mirrors the REST query params** with the same names and bounds: REST symbols match `^[A-Z0-9-]{1,8}$` after normalization, while the tool schemas are more permissive (`^[A-Za-z0-9.-]{1,8}$`) and lowercase or dot forms like `brk.b` normalize server-side. `limit` is 1–100 for filings and 1–200 for insider, section `name` is one of `business`, `risk_factors`, `mda`, `quantitative_qualitative_disclosures`, `controls_and_procedures`, `press_release`. The linked endpoint page is the full reference for each tool's parameters and response fields. # Company profile API (/docs/api-reference/endpoints/company-context/profile) `GET https://api.stockcontext.com/v2/profile` Who the company is: description, sector and industry, exchange, headquarters, website, CIK, market cap, and shares outstanding. ETF profiles return fund facts and top-10 N-PORT holdings where collected; otherwise holdings return a machine-readable unavailable reason. N-PORT data is filed with a ~60-day lag. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T18:32:32Z", "asset_type": "stock", "auditor": { "accession": "0000320193-25-000079", "filed": "2025-10-31", "form": "10-K", "location": "San Jose, California", "name": "Ernst & Young LLP", "pcaob_firm_id": "42" }, "cache_age_seconds": 48, "cik": "0000320193", "concentration": { "basis": "single_period", "business": { "hhi": 0.36, "n_segments": 5, "top_segment_share_pct": 51.3, "top_segments": [ { "name": "iPhone", "share_pct": 51.3 }, { "name": "Services", "share_pct": 27.9 }, { "name": "Mac", "share_pct": 7.6 } ] }, "geography": { "top_regions": [ { "name": "Americas", "share_pct": 40.6 }, { "name": "Europe", "share_pct": 25.2 }, { "name": "Greater China", "share_pct": 18.4 } ] }, "period_end": "2026-03-28" }, "country": "US", "currency": "USD", "description": "Business Company Background The Company designs, manufactures and markets smartphones, personal computers, tablets, wearables and accessories, and sells a variety of related services. The Company’s fiscal year is the 52- or 53-week period that ends on the last Saturday of September. Products iPhone iPhone® is the Company’s line of smartphones based on its iOS operating system. The iPhone line includes iPhone 17 Pro, iPhone Air™, iPhone 17, iPhone 16 and iPhone 16e. Mac Mac® is the Company’s", "dividend_yield_pct": 0.37, "exchange": "NASDAQ", "filer_category": { "accession": "0000320193-25-000079", "filed": "2025-10-31", "form": "10-K", "value": "Large Accelerated Filer" }, "fiscal_year_end_month": 9, "freshness": "end_of_day", "headquarters": "Cupertino, CA", "industry": "Electronic Computers", "market_cap_usd": 4266970665120, "market_status": "open", "name": "Apple Inc", "public_float": { "accession": "0000320193-25-000079", "as_of": "2025-03-28", "filed": "2025-10-31", "form": "10-K", "value_usd": 3253431000000 }, "sector": "Information Technology", "share_count_basis": "listed_security", "shares_outstanding": 14687356000, "sic": "3571", "sic_description": "Electronic Computers", "symbol": "AAPL", "website": null }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY) ```json { "data": { "as_of": "2026-06-12T18:33:20Z", "asset_type": "etf", "cache_age_seconds": 0, "currency": "USD", "description": "Historical ETF prices for SPDR S&P 500 ETF (SPY). SPDR S&P 500 ETF Trust (the Trust) is an exchange traded fund. The Trust corresponds to the price and yield performance of the S&P 500 Index. The S&P 500 Index is composed of 500 selected stocks and spans over 24 separate industry groups. The Fund's investment sectors include information technology, financials, energy, health care, consumer staples, industrials, consumer discretionary, materials, utilities and telecommunication services.", "dividend_yield_pct": 0.97, "exchange": "NYSE", "freshness": "end_of_day", "issuer": "SPDR S&P 500 ETF TRUST", "market_status": "open", "name": "SPDR S&P 500 ETF Trust", "shares_outstanding": { "available": false, "reason": "etf_shares_outstanding_unavailable_upstream" }, "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/profile?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/profile", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/profile?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock comparison API (/docs/api-reference/endpoints/core-context/compare) `GET https://api.stockcontext.com/v2/compare` A 2–12 symbol scorecard over selected snapshot and technicals fields. Fields a symbol cannot supply are listed per symbol in `fields_omitted_by_symbol` instead of appearing as null. More than 12 symbols is `PARAM_INVALID`. Compare first, then deepen only finalists. Tip: add SPY to the basket for same-day market-relative context. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbols` | query | yes | Comma-separated list of 2-12 distinct ticker symbols. Each symbol is validated against the supported universe. | | `fields` | query | no | Comma-separated subset of compare fields. Allowed values: price, pct_1d, pct_1m, pct_ytd, pct_1y, vs_spy_pct_1y, range_52w_position_pct, drawdown_from_high_pct, market_cap_usd, pe_ttm, ps_ttm, pb, ev_ebitda_ttm, fcf_yield_pct, dividend_yield_pct, beta_5y_weekly, rsi_14, rsi_zone, trending, golden_cross_active, vs_sma_200_pct, distance_from_52w_high_pct. Defaults when omitted: price, pct_1y, vs_spy_pct_1y, pe_ttm, rsi_14, trending. | ## Response examples (captured from production) ### Three-way (NVDA, AMD, INTC) ```json { "data": { "as_of": "2026-06-12T14:12:23Z", "cache_age_seconds": 0, "fields_omitted_by_symbol": {}, "fields_requested": [ "price", "pct_1y", "vs_spy_pct_1y", "pe_ttm", "rsi_14", "trending" ], "freshness": "intraday", "market_status": "open", "symbols": [ { "pct_1y": 41.99, "pe_ttm": 31.49, "price": 205.62, "rsi_14": 45.09, "symbol": "NVDA", "trending": false, "vs_spy_pct_1y": 17.9 }, { "pct_1y": 333.63, "pe_ttm": 168.48, "price": 513.85, "rsi_14": 56.88, "symbol": "AMD", "trending": true, "vs_spy_pct_1y": 309.54 }, { "flags": [ "pe_ttm_negative_not_meaningful" ], "pct_1y": 488.16, "pe_ttm": -197.03, "price": 122.16, "rsi_14": 57.79, "symbol": "INTC", "trending": true, "vs_spy_pct_1y": 464.07 } ] }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/compare?symbols=NVDA,AMD,INTC" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/compare", params={"symbols": "NVDA,AMD,INTC"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/compare?symbols=NVDA,AMD,INTC", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Reverse-DCF API (what is priced in) (/docs/api-reference/endpoints/core-context/priced-in) `GET https://api.stockcontext.com/v2/priced-in` A reverse discounted-cash-flow MODEL: it solves for the FCF growth rate that today's enterprise value already requires, under fixed, clearly-labeled assumptions (10% discount, 10-year horizon fading to 2.5% terminal). The headline is `implied.growth_required_pct` — what the PRICE REQUIRES, not a forecast, fair value, or price target (no price field is ever emitted). It ships beside a 3x3 discount-by-terminal `sensitivity` grid (the answer moves with the discount rate, so there is no single 'true' number) and `historical_realized_growth` — the company's OWN trailing FCF and revenue CAGRs at 3y/5y, with base-effect and window-disagreement flags and a signed `implied_vs_5y_realized_pp` gap. Trailing growth is what the company DID, never the benchmark the implied figure must beat. Refuses (with a `reason`) for ETFs, banks and insurers, REITs, and pre-profit companies, where a reverse-DCF on their economics misleads. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (NVDA) ```json { "data": { "anchor": { "discounted_against": "enterprise_value", "enterprise_value_usd": 4926590000000, "net_debt_period_end": "2026-03-28", "share_count_basis": "listed_security", "ttm_fcf_usd": 252245627638 }, "as_of": "2026-06-05T20:15:00Z", "basis": "fcf_ttm", "cache_age_seconds": 2612, "currency": "USD", "freshness": "end_of_day", "historical_realized_growth": { "fcf_cagr": { "10y": { "available": false, "reason": "insufficient_statement_history" }, "3y": { "value": 44.42 }, "5y": { "base_effect": "low_base_period", "base_note": "the 5y-ago fcf base was about 16% of today's, so the CAGR is mechanically inflated by the small starting value", "value": 44.42 } }, "implied_vs_5y_realized_pp": { "caveat": "5y_realized_base_unreliable", "value": -33.02 }, "revenue_cagr": { "10y": { "available": false, "reason": "insufficient_statement_history" }, "3y": { "value": 27.68 }, "5y": { "value": 27.68 } } }, "implied": { "growth_required_pct": { "converged": true, "means": "To justify today's enterprise value at a 10.0% discount over 10 years (fading to 2.5% terminal), free cash flow must compound at about 11.4%/yr. This is what the PRICE REQUIRES, not a forecast.", "solver": "bisection", "value": 11.4 } }, "market_status": "open", "model_assumptions": { "all_fixed_defaults": true, "discount_rate_pct": 10, "growth_fade": "linear_to_terminal", "horizon_years": 10, "terminal_growth_pct": 2.5 }, "sensitivity": { "discount_rate_pct_rows": [ 8, 10, 12 ], "grid": [ [ 5.3, 3.6, 1.8 ], [ 12.7, 11.4, 10 ], [ 19.1, 18, 16.8 ] ], "metric": "growth_required_pct", "terminal_growth_pct_cols": [ 2, 2.5, 3 ] }, "shape_kind": "reverse_dcf", "symbol": "NVDA" }, "schema_version": "2026-06-17.6" } ``` ### Bank (JPM): not applicable Banks have no meaningful free cash flow or enterprise value, so the model refuses with `available: false` and a `reason`. ```json { "data": { "anchor": null, "as_of": "2026-06-05T20:15:00Z", "available": false, "cache_age_seconds": 2612, "currency": "USD", "freshness": "end_of_day", "market_status": "open", "reason": "reverse_dcf_not_meaningful_financial_company", "shape_kind": "not_applicable", "symbol": "JPM" }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY): unsupported ETFs short-circuit before any fetch; the envelope carries `freshness: "unsupported"` and `reason: "etf_no_reverse_dcf"`. ```json { "data": { "as_of": "2026-06-05T20:15:00Z", "asset_type": "etf", "cache_age_seconds": 0, "freshness": "unsupported", "market_status": "closed", "reason": "etf_no_reverse_dcf", "shape_kind": "not_applicable", "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/priced-in?symbol=NVDA" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/priced-in", params={"symbol": "NVDA"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/priced-in?symbol=NVDA", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock quote & snapshot API (/docs/api-reference/endpoints/core-context/snapshot) `GET https://api.stockcontext.com/v2/snapshot` One-call decision context: live quote when available, performance over seven windows plus relative-to-SPY, 52-week range position, and a `fundamentals_quick` block. If the quote provider cannot serve the live stock quote, the route still returns the rest of the snapshot with `quote: {available: false, reason: "price_quote_unavailable"}` and dependent share-price-basis fields unavailable. Each multiple in `fundamentals_quick` carries `{value, vs_5y}`, where `vs_5y` bands the multiple against the symbol's OWN five-year history (`near_5y_low` to `near_5y_high`) — a low band is not a cheapness verdict versus peers or fair value, and `vs_5y` is omitted when history is too thin. The shape differs by asset type: ETF responses omit `market_cap_usd` and return `{available: false, reason}` objects for earnings-based ratios instead of numbers, so handle both. Access: all plans, including Free. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T14:12:18Z", "asset_type": "stock", "cache_age_seconds": 0, "currency": "USD", "events": { "latest_filing": { "accession": "0000320193-26-000013", "filing_date": "2026-05-01", "form": "10-Q" }, "next_earnings_date_estimated": "2026-07-29" }, "freshness": "intraday", "fundamentals_quick": { "beta_5y_weekly": 1.17, "dividend_yield_pct": 0.37, "enterprise_value_usd": 4303149469280, "ev_ebitda_ttm": { "value": 26.9, "vs_5y": "near_5y_high", "z_5y": 1.34 }, "fcf_yield_pct": 3.01, "market_cap_usd": 4286945469280, "pb": { "value": 40.26, "vs_5y": "below_5y_median", "z_5y": -0.67 }, "pe_ttm": { "value": 35.29, "vs_5y": "near_5y_high", "z_5y": 1.18 }, "ps_ttm": { "value": 9.5, "vs_5y": "near_5y_high", "z_5y": 1.62 }, "share_count_basis": "listed_security", "shares_outstanding": 14687356000, "ttm_eps_diluted": 8.27, "ttm_net_income_usd": 122575000000, "ttm_revenue_usd": 451442000000 }, "industry": "Electronic Computers", "market_status": "open", "name": "Apple Inc.", "performance": { "pct_1d": -1.27, "pct_1m": -2.34, "pct_1y": 47.11, "pct_3m": 16.8, "pct_3y": 63.09, "pct_5d": -5.03, "pct_ytd": 7.9, "vs_sector_pct_1y": -6.21, "vs_spy_pct_1m": -2.28, "vs_spy_pct_1y": 23.02, "vs_spy_pct_ytd": -0.39 }, "quote": { "change_abs": -3.75, "change_pct": -1.27, "day_high": 297.17, "day_low": 290.61, "day_open": 296.65, "previous_close": 295.63, "price": 291.88, "volume": 42275511 }, "range_52w": { "drawdown_from_high_pct": -8.04, "high": 317.4, "low": 194.3, "position_pct": 79.3 }, "sector": "Information Technology", "sic": "3571", "sic_description": "Electronic Computers", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY) Note the `{available: false, reason}` envelopes inside `fundamentals_quick` and the absent `market_cap_usd`. ```json { "data": { "as_of": "2026-06-12T14:12:19Z", "asset_type": "etf", "benchmark_role": "market_proxy", "cache_age_seconds": 0, "currency": "USD", "etf_category": "broad_market", "etf_focus": "S&P 500 large-cap U.S. equity exposure", "events": { "available": false, "reason": "not_applicable_for_etf" }, "freshness": "intraday", "fundamentals_quick": { "beta_5y_weekly": 1, "dividend_yield_pct": 0.97, "enterprise_value_usd": { "available": false, "reason": "etf_enterprise_value_not_applicable" }, "ev_ebitda_ttm": { "available": false, "reason": "etf_no_ebitda" }, "fcf_yield_pct": { "available": false, "reason": "etf_no_cash_flow_statements" }, "pb": { "available": false, "reason": "etf_no_book_value" }, "pe_ttm": { "available": false, "reason": "etf_no_earnings" }, "ps_ttm": { "available": false, "reason": "etf_no_revenue" }, "shares_outstanding": { "available": false, "reason": "etf_shares_outstanding_unavailable_upstream" }, "ttm_eps_diluted": { "available": false, "reason": "etf_no_eps" }, "ttm_net_income_usd": { "available": false, "reason": "etf_no_net_income" }, "ttm_revenue_usd": { "available": false, "reason": "etf_no_revenue" } }, "market_status": "open", "name": "SPDR S&P 500 ETF Trust", "performance": { "pct_1d": 0.29, "pct_1m": -0.33, "pct_1y": 23.95, "pct_3m": 12.02, "pct_3y": 76.74, "pct_5d": 0.31, "pct_ytd": 8.6, "vs_sector_pct_1y": { "available": false, "reason": "not_applicable_for_etf" }, "vs_spy_pct_1m": 0, "vs_spy_pct_1y": 0, "vs_spy_pct_ytd": 0 }, "quote": { "change_abs": 2.11, "change_pct": 0.29, "day_high": 742.8, "day_low": 734.9, "day_open": 742.67, "previous_close": 737.76, "price": 739.87, "volume": 78452039 }, "range_52w": { "drawdown_from_high_pct": -2.7, "high": 760.4, "low": 586.91, "position_pct": 88.2 }, "sector": "Broad Market", "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/snapshot?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/snapshot", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/snapshot?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock valuation API (vs own history) (/docs/api-reference/endpoints/core-context/valuation) `GET https://api.stockcontext.com/v2/valuation` Seven multiples (P/E, P/S, P/B, EV/EBITDA, EV/EBIT, EV/Sales, PEG), each with a `vs_own_history` block that ranks the current value against THIS symbol's OWN history only — percentiles across the windows that have enough clean data (3y, 5y, 10y), a median anchor, and a `label` band (`near_5y_low`, `below_5y_median`, `near_5y_median`, `above_5y_median`, `near_5y_high`, or the `_3y_` variants). A low band is NOT a cheapness verdict versus peers, sector, or fair value — it is position within the symbol's own range. P/E, P/S, and the anchor also carry a `change` block attributing the multiple's 1y and 3y move to its price and fundamental legs (the legs combine multiplicatively, not additively). A top-level `history_regime` flags when revenue or EPS changed scale enough that the percentile axis is partly ranking today's company against one that no longer exists, and `fundamentals_trajectory` ships the latest growth and its direction so a low percentile is read next to decelerating growth. Plus five yields and returns on capital. Values and context that do not apply return `{available: false, reason}` objects, never zero. ETFs return `freshness: "unsupported"`. Access: all plans, including Free. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (NVDA) ```json { "data": { "as_of": "2026-06-05T20:15:00Z", "cache_age_seconds": 2612, "currency": "USD", "enterprise_value_usd": 4926590000000, "freshness": "end_of_day", "fundamentals_trajectory": { "eps_growth": { "latest_yoy_pct": 44.42, "trend": "stable" }, "gross_margin": { "latest_pct": 75, "trend": "stable" }, "revenue_growth": { "latest_yoy_pct": 27.68, "trend": "stable" } }, "history_regime": { "drivers": [ { "change_x": 3.2, "direction": "growth", "metric": "revenue" }, { "change_x": 5.7, "direction": "growth", "metric": "eps" } ], "regime_break": true, "window": "5y" }, "market_cap_usd": 4920090000000, "market_status": "open", "multiples": { "ev_ebit_ttm": { "value": 37.67, "vs_own_history": { "label": "near_5y_low", "median_5y": 64.93, "n": 1304, "percentile": { "3y": 6, "5y": 4 }, "z_3y": -1.44, "z_5y": -1.54 } }, "ev_ebitda_ttm": { "value": 34.53, "vs_own_history": { "label": "near_5y_low", "median_5y": 59.52, "n": 1304, "percentile": { "3y": 6, "5y": 4 }, "z_3y": -1.44, "z_5y": -1.54 } }, "ev_sales_ttm": { "value": 20.72, "vs_own_history": { "label": "near_5y_low", "median_5y": 35.71, "n": 1304, "percentile": { "3y": 6, "5y": 4 }, "z_3y": -1.44, "z_5y": -1.54 } }, "pb": { "value": 37.85, "vs_own_history": { "label": "near_5y_low", "median_5y": 51.13, "n": 1304, "percentile": { "3y": 0, "5y": 0 }, "z_3y": -2.84, "z_5y": -2.58 } }, "pe_ttm": { "change": { "1y": { "driver": "fundamentals_outran_price", "fundamental_pct": 44.42, "price_pct": 2.08 }, "3y": { "driver": "fundamentals_outran_price", "fundamental_pct": 201.21, "price_pct": 6.54 } }, "value": 19.5, "vs_own_history": { "label": "near_5y_low", "median_5y": 81.26, "n": 1304, "percentile": { "3y": 0, "5y": 0 }, "z_3y": -2.39, "z_5y": -2.33 } }, "ps_ttm": { "change": { "1y": { "driver": "fundamentals_outran_price", "fundamental_pct": 27.68, "price_pct": 2.08 }, "3y": { "driver": "fundamentals_outran_price", "fundamental_pct": 108.16, "price_pct": 6.54 } }, "value": 20.69, "vs_own_history": { "label": "near_5y_low", "median_5y": 36.07, "n": 1304, "percentile": { "3y": 6, "5y": 4 }, "z_3y": -1.48, "z_5y": -1.57 } } }, "returns_on_capital": { "flags": [], "roa_pct": 141.44, "roe_pct": 212.16, "roic_pct": { "trend": { "available": false, "reason": "insufficient_trend_history" }, "value": 100 } }, "share_count_basis": "listed_security", "symbol": "NVDA", "valuation_context": { "anchor": "pe_ttm", "label": "near_5y_low", "rationale": "positive_earnings_period", "value": 19.5 }, "yields": { "buyback_yield_pct_ttm": { "available": false, "reason": "insufficient_buyback_activity" }, "dividend_yield_pct": { "available": false, "reason": "no_dividend_payer" }, "earnings_yield_pct": 5.13, "fcf_yield_pct": 5.13, "total_payout_yield_pct_ttm": { "available": false, "reason": "insufficient_payout_history" } } }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY): unsupported ETFs have no valuation multiples; the success envelope carries `freshness: "unsupported"` and a `reason`. ```json { "data": { "as_of": "2026-06-12T14:12:21Z", "asset_type": "etf", "cache_age_seconds": 0, "freshness": "unsupported", "market_status": "open", "reason": "etf_no_valuation_multiples", "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ### Non-payer (TSLA) `dividend_yield_pct` is an `{available: false, reason: "no_dividend_payer"}` object, not 0. ```json { "data": { "as_of": "2026-06-05T20:15:00Z", "cache_age_seconds": 2612, "currency": "USD", "enterprise_value_usd": 1297002000000, "freshness": "end_of_day", "fundamentals_trajectory": { "eps_growth": { "latest_yoy_pct": 7.37, "trend": "stable" }, "gross_margin": { "latest_pct": 20, "trend": "stable" }, "revenue_growth": { "latest_yoy_pct": 5.38, "trend": "stable" } }, "history_regime": { "drivers": [], "regime_break": false, "window": "5y" }, "market_cap_usd": 1300002000000, "market_status": "open", "multiples": { "ev_ebit_ttm": { "value": 110.22, "vs_own_history": { "label": "near_5y_high", "median_5y": 58.36, "n": 1304, "percentile": { "3y": 100, "5y": 100 }, "z_3y": 1.7, "z_5y": 1.91 } }, "ev_ebitda_ttm": { "value": 78.73, "vs_own_history": { "label": "near_5y_high", "median_5y": 41.68, "n": 1304, "percentile": { "3y": 100, "5y": 100 }, "z_3y": 1.7, "z_5y": 1.91 } }, "ev_sales_ttm": { "value": 11.02, "vs_own_history": { "label": "near_5y_high", "median_5y": 5.84, "n": 1304, "percentile": { "3y": 100, "5y": 100 }, "z_3y": 1.7, "z_5y": 1.91 } }, "pb": { "value": 17.33, "vs_own_history": { "label": "near_5y_high", "median_5y": 11.09, "n": 1304, "percentile": { "3y": 92, "5y": 95 }, "z_3y": 1.45, "z_5y": 1.76 } }, "pe_ttm": { "change": { "1y": { "driver": "price_outran_fundamentals", "fundamental_pct": 7.37, "price_pct": 26.95 }, "3y": { "driver": "price_outran_fundamentals", "fundamental_pct": 23.77, "price_pct": 166.78 } }, "value": 111.28, "vs_own_history": { "label": "near_5y_high", "median_5y": 47.46, "n": 1304, "percentile": { "3y": 100, "5y": 100 }, "z_3y": 2.72, "z_5y": 2.87 } }, "ps_ttm": { "change": { "1y": { "driver": "price_outran_fundamentals", "fundamental_pct": 5.38, "price_pct": 26.95 }, "3y": { "driver": "price_outran_fundamentals", "fundamental_pct": 17.02, "price_pct": 166.78 } }, "value": 11.05, "vs_own_history": { "label": "near_5y_high", "median_5y": 5.89, "n": 1304, "percentile": { "3y": 100, "5y": 100 }, "z_3y": 1.65, "z_5y": 1.87 } } }, "returns_on_capital": { "flags": [], "roa_pct": 11.35, "roe_pct": 15.89, "roic_pct": { "trend": { "available": false, "reason": "insufficient_trend_history" }, "value": 15.38 } }, "share_count_basis": "listed_security", "symbol": "TSLA", "valuation_context": { "anchor": "pe_ttm", "label": "near_5y_high", "rationale": "positive_earnings_period", "value": 111.28 }, "yields": { "buyback_yield_pct_ttm": { "available": false, "reason": "insufficient_buyback_activity" }, "dividend_yield_pct": { "available": false, "reason": "no_dividend_payer" }, "earnings_yield_pct": 0.9, "fcf_yield_pct": 0.6, "total_payout_yield_pct_ttm": { "available": false, "reason": "insufficient_payout_history" } } }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/valuation?symbol=NVDA" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/valuation", params={"symbol": "NVDA"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/valuation?symbol=NVDA", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock data coverage API (/docs/api-reference/endpoints/discovery/coverage) `GET https://api.stockcontext.com/v2/coverage` Per-symbol support map: the asset type plus a `supported` flag (with a `reason` when false) for every data family: snapshot, valuation, fundamentals, filings, insider, and the rest. One call tells you which endpoints apply to a symbol before you make any of them. Access: all plans, including Free. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "asset_type": "stock", "cache_age_seconds": 2612, "support": { "calendar": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "calendar_refresh_daily_with_sec_event_epoch_invalidation", "lane": "company_calendar", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_submissions", "earnings_8k_events", "calendar_estimator" ], "supported": true }, "dividends": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "market_inputs_as_of_response_sec_payout_inputs_within_15_to_20_minutes", "lane": "dividends_and_payouts", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "payment_history_as_of_vendor_snapshot_sec_payouts_filing_date_anchored", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "dividend_history", "sec_cash_flow_facts", "published_fundamentals" ], "supported": true }, "earnings": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "filing_or_calendar_updates_within_15_to_20_minutes_when_sec_sourced", "lane": "earnings_actuals_and_calendar", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_8k_earnings_releases", "sec_submissions", "calendar_estimator" ], "supported": true }, "events": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "event_source_ambiguous" ], "freshness_slo": "sec_poller_every_5_minutes_target_15_to_20_minutes_to_served", "lane": "sec_event_filings", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_8k_items", "sec_event_forms", "sec_submissions_json" ], "supported": true }, "facts": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": true, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "point_in_time_not_available", "concept_not_in_catalog_without_unavailable_signal" ], "freshness_slo": "companyfacts_sweep_daily_plus_current_filing_rebuild_within_15_to_20_minutes", "lane": "raw_sec_companyfacts_and_curated_pit_grid", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_companyfacts_json", "sec_submissions_json" ], "supported": true }, "filings": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "missing_accession_or_unparsed_pagination" ], "freshness_slo": "sec_poller_every_5_minutes_target_15_to_20_minutes_to_served", "lane": "sec_submissions_and_filing_inventory", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_submissions_json", "sec_current_filings_atom", "sec_daily_index" ], "supported": true }, "fundamentals": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection", "unit_or_period_mismatch", "statement_coherence_failure" ], "freshness_slo": "standard_sec_filings_served_within_15_to_20_minutes", "lane": "normalized_core_fundamentals", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_companyfacts", "filing_statement_maps", "sec_submissions", "market_data_for_price_joins" ], "supported": true }, "governance": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": true, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "low_confidence_governance_extraction" ], "freshness_slo": "latest_proxy_detection_same_day_parsed_fields_best_effort_until_gate_green", "lane": "proxy_governance_and_ecd", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "private_beta", "quality_grade": "production_beta", "route_supported": true, "source_priority": [ "sec_def14a", "sec_10k_governance_source", "sec_8k_governance_updates" ], "supported": true }, "history": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "derived_series_available": true, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "price_history_as_of_response_derived_series_after_sec_publish", "lane": "historical_prices_and_filing_anchored_derived_series", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "price_history_as_of_vendor_snapshot_derived_series_filing_date_anchored", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history", "published_derived_daily_sec_series" ], "supported": true }, "insider": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "parsed_transaction_missing_required_field" ], "freshness_slo": "form4_events_target_within_15_to_20_minutes_to_served", "lane": "section_16_form4_insider_transactions", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_ownership_xml", "sec_submissions_json" ], "supported": true }, "ownership": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": true, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "beneficial_owner_or_position_missing_required_field", "cusip_mapping_unresolved_for_exact_security_claim" ], "freshness_slo": "coverage_flagged_backfill_lane_13f_always_report_period_lagged", "lane": "ownership_13dg_13f_and_section16_context", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "private_beta", "quality_grade": "production_beta", "route_supported": true, "source_priority": [ "sec_schedule_13d_13g", "sec_13f_information_table", "sec_form4_xml" ], "supported": true }, "price_action": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "market_data_as_of_response", "lane": "market_price_action", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "market_data_as_of_timestamped_not_sec_point_in_time", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history" ], "supported": true }, "priced_in": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "non_positive_or_unsupported_cash_flow_base" ], "freshness_slo": "market_inputs_as_of_response_sec_inputs_within_15_to_20_minutes", "lane": "deterministic_reverse_dcf", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_input_vintages", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "published_fundamentals", "market_data" ], "supported": true }, "profile": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "route_or_asset_class_unsupported" ], "freshness_slo": "daily_entity_refresh_plus_sec_filing_epoch_invalidation", "lane": "entity_profile", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_company_tickers", "sec_submissions", "market_metadata" ], "supported": true }, "segments": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": true, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "segment_axis_or_member_missing", "segment_total_generated_from_unsupported_axes" ], "freshness_slo": "coverage_flagged_backfill_lane_not_claimed_complete_until_g_seg_green", "lane": "filing_level_segment_facts", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "filing_date_anchored_segment_snapshot_with_axis_member_identity", "public_visibility": "private_beta", "quality_grade": "production_beta", "route_supported": true, "source_priority": [ "filing_level_inline_xbrl_contexts", "filing_level_xbrl_linkbases", "sec_submissions_json" ], "supported": true }, "snapshot": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "warm_path_under_2s_sec_inputs_within_15_to_20_minutes", "lane": "composite_company_context", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "published_redis_snapshot", "market_data", "sec_companyfacts", "sec_submissions" ], "supported": true }, "technicals": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "market_data_as_of_response", "lane": "market_technicals", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "market_data_as_of_timestamped_not_sec_point_in_time", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history" ], "supported": true }, "valuation": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "market_inputs_as_of_response_sec_inputs_within_15_to_20_minutes", "lane": "valuation_from_filed_fundamentals_and_market_data", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "filing_date_anchored_fundamentals_joined_to_market_as_of", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "published_fundamentals", "market_data", "sec_companyfacts" ], "supported": true } }, "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY) ETFs support market-data families; earnings, fundamentals, filings, and insider report `supported: false` with a reason. ```json { "data": { "asset_type": "etf", "cache_age_seconds": 2612, "support": { "calendar": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "calendar_refresh_daily_with_sec_event_epoch_invalidation", "lane": "company_calendar", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_submissions", "earnings_8k_events", "calendar_estimator" ], "supported": true }, "dividends": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "market_inputs_as_of_response_sec_payout_inputs_within_15_to_20_minutes", "lane": "dividends_and_payouts", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "payment_history_as_of_vendor_snapshot_sec_payouts_filing_date_anchored", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "dividend_history", "sec_cash_flow_facts", "published_fundamentals" ], "supported": true }, "earnings": { "available_now": false, "blocker": "etf_no_earnings_reports", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "filing_or_calendar_updates_within_15_to_20_minutes_when_sec_sourced", "lane": "earnings_actuals_and_calendar", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "etf_no_earnings_reports", "route_supported": false, "source_priority": [ "sec_8k_earnings_releases", "sec_submissions", "calendar_estimator" ], "supported": false }, "events": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "event_source_ambiguous" ], "freshness_slo": "sec_poller_every_5_minutes_target_15_to_20_minutes_to_served", "lane": "sec_event_filings", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "sec_8k_items", "sec_event_forms", "sec_submissions_json" ], "supported": false }, "facts": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "point_in_time_not_available", "concept_not_in_catalog_without_unavailable_signal" ], "freshness_slo": "companyfacts_sweep_daily_plus_current_filing_rebuild_within_15_to_20_minutes", "lane": "raw_sec_companyfacts_and_curated_pit_grid", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "sec_companyfacts_json", "sec_submissions_json" ], "supported": false }, "filings": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "missing_accession_or_unparsed_pagination" ], "freshness_slo": "sec_poller_every_5_minutes_target_15_to_20_minutes_to_served", "lane": "sec_submissions_and_filing_inventory", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "sec_submissions_json", "sec_current_filings_atom", "sec_daily_index" ], "supported": false }, "fundamentals": { "available_now": false, "blocker": "etf_no_standalone_financial_statements", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection", "unit_or_period_mismatch", "statement_coherence_failure" ], "freshness_slo": "standard_sec_filings_served_within_15_to_20_minutes", "lane": "normalized_core_fundamentals", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "etf_no_standalone_financial_statements", "route_supported": false, "source_priority": [ "sec_companyfacts", "filing_statement_maps", "sec_submissions", "market_data_for_price_joins" ], "supported": false }, "governance": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "low_confidence_governance_extraction" ], "freshness_slo": "latest_proxy_detection_same_day_parsed_fields_best_effort_until_gate_green", "lane": "proxy_governance_and_ecd", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "private_beta", "quality_grade": "production_beta", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "sec_def14a", "sec_10k_governance_source", "sec_8k_governance_updates" ], "supported": false }, "history": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "derived_series_available": false, "derived_series_blocker": "derived_history_not_available", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "price_history_as_of_response_derived_series_after_sec_publish", "lane": "historical_prices_and_filing_anchored_derived_series", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "price_history_as_of_vendor_snapshot_derived_series_filing_date_anchored", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history", "published_derived_daily_sec_series" ], "supported": true }, "insider": { "available_now": false, "blocker": "etf_no_insider_transactions", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "parsed_transaction_missing_required_field" ], "freshness_slo": "form4_events_target_within_15_to_20_minutes_to_served", "lane": "section_16_form4_insider_transactions", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "etf_no_insider_transactions", "route_supported": false, "source_priority": [ "sec_ownership_xml", "sec_submissions_json" ], "supported": false }, "ownership": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "beneficial_owner_or_position_missing_required_field", "cusip_mapping_unresolved_for_exact_security_claim" ], "freshness_slo": "coverage_flagged_backfill_lane_13f_always_report_period_lagged", "lane": "ownership_13dg_13f_and_section16_context", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "as_of_filters_by_sec_filed_or_accepted_date_without_lookahead", "public_visibility": "private_beta", "quality_grade": "production_beta", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "sec_schedule_13d_13g", "sec_13f_information_table", "sec_form4_xml" ], "supported": false }, "price_action": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "market_data_as_of_response", "lane": "market_price_action", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "market_data_as_of_timestamped_not_sec_point_in_time", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history" ], "supported": true }, "priced_in": { "available_now": false, "blocker": "etf_no_reverse_dcf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "non_positive_or_unsupported_cash_flow_base" ], "freshness_slo": "market_inputs_as_of_response_sec_inputs_within_15_to_20_minutes", "lane": "deterministic_reverse_dcf", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_input_vintages", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "etf_no_reverse_dcf", "route_supported": false, "source_priority": [ "published_fundamentals", "market_data" ], "supported": false }, "profile": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "route_or_asset_class_unsupported" ], "freshness_slo": "daily_entity_refresh_plus_sec_filing_epoch_invalidation", "lane": "entity_profile", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "sec_company_tickers", "sec_submissions", "market_metadata" ], "supported": true }, "segments": { "available_now": false, "blocker": "not_applicable_for_etf", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "segment_axis_or_member_missing", "segment_total_generated_from_unsupported_axes" ], "freshness_slo": "coverage_flagged_backfill_lane_not_claimed_complete_until_g_seg_green", "lane": "filing_level_segment_facts", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "filing_date_anchored_segment_snapshot_with_axis_member_identity", "public_visibility": "private_beta", "quality_grade": "production_beta", "reason": "not_applicable_for_etf", "route_supported": false, "source_priority": [ "filing_level_inline_xbrl_contexts", "filing_level_xbrl_linkbases", "sec_submissions_json" ], "supported": false }, "snapshot": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "warm_path_under_2s_sec_inputs_within_15_to_20_minutes", "lane": "composite_company_context", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "latest_published_snapshot_with_source_dates", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "published_redis_snapshot", "market_data", "sec_companyfacts", "sec_submissions" ], "supported": true }, "technicals": { "available_now": true, "coverage_universe": "active_us_exchange_listed_and_trading_v1", "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status" ], "freshness_slo": "market_data_as_of_response", "lane": "market_technicals", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "market_data_as_of_timestamped_not_sec_point_in_time", "public_visibility": "public_core", "quality_grade": "production_core", "route_supported": true, "source_priority": [ "price_history" ], "supported": true }, "valuation": { "available_now": false, "blocker": "etf_no_valuation_multiples", "coverage_universe": "active_us_exchange_listed_and_trading_v1", "data_collected": false, "fail_closed_conditions": [ "required_source_missing_or_not_yet_collected", "stale_required_source_without_explicit_stale_status", "ambiguous_core_value_without_deterministic_selection" ], "freshness_slo": "market_inputs_as_of_response_sec_inputs_within_15_to_20_minutes", "lane": "valuation_from_filed_fundamentals_and_market_data", "missing_data_policy": "value_or_envelope_with_status_reason_and_citations", "point_in_time_semantics": "filing_date_anchored_fundamentals_joined_to_market_as_of", "public_visibility": "public_core", "quality_grade": "production_core", "reason": "etf_no_valuation_multiples", "route_supported": false, "source_priority": [ "published_fundamentals", "market_data", "sec_companyfacts" ], "supported": false } }, "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/coverage?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/coverage", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/coverage?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock symbol search API (/docs/api-reference/endpoints/discovery/search) `GET https://api.stockcontext.com/v2/search` Resolve a ticker fragment or company name to supported symbols. Returns ranked matches with symbol, name, asset type, and exchange. Call this before storing user-entered input: unknown symbols fail later calls with `SYMBOL_UNKNOWN`. Access: all plans, including Free. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `query` | query | no | Free-text search query matched against ticker symbol and issuer name. Case-insensitive; whitespace and punctuation are normalized. Also resolves curated aliases and themes: a colloquial name maps to its ticker (facebook -> META), a punctuated class symbol resolves through its grammar form (brkb -> BRK-B), and theme phrases return the relevant cohort ('electric vehicle' -> TSLA, RIVN, LCID, ...). | | `q` | query | no | Alias for `query` (back-compat). | ## Response examples (captured from production) ### Name search ```json { "data": { "cache_age_seconds": 0, "matches": [ { "asset_type": "stock", "exchange": "NASDAQ", "name": "Apple Inc.", "symbol": "AAPL" }, { "asset_type": "stock", "exchange": "NYSE", "name": "Apple Hospitality REIT, Inc.", "symbol": "APLE" }, { "asset_type": "stock", "exchange": "NYSE", "name": "MAUI LAND & PINEAPPLE CO INC", "symbol": "MLP" }, { "asset_type": "stock", "exchange": "NYSE AMERICAN", "name": "Pineapple Financial Inc.", "symbol": "PAPL" } ], "query": "apple" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/search?query=apple" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/search", params={"query": "apple"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/search?query=apple", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Earnings calendar API (per symbol) (/docs/api-reference/endpoints/events/calendar) `GET https://api.stockcontext.com/v2/calendar` Upcoming per-symbol events (estimated next earnings and dividend dates) derived from that symbol's own statement and dividend history. Not a market-wide calendar. When nothing can be estimated, `upcoming` is the object `{available: false, reason: "no_supported_events_estimated"}` rather than an empty array, so branch on that before treating `upcoming` as a list. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T14:12:24Z", "cache_age_seconds": 0, "freshness": "end_of_day", "market_status": "open", "symbol": "AAPL", "upcoming": [ { "date": "2026-07-29", "details": "Estimated from quarterly reporting cadence", "estimated": true, "type": "earnings" }, { "date": "2026-07-31", "details": "Expected SEC filing based on recent statement filing cadence", "estimated": true, "type": "filing_expected" }, { "date": "2026-08-10", "details": "$0.27 quarterly cadence", "estimated": true, "type": "ex_dividend" } ] }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/calendar?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/calendar", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/calendar?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # SEC event stream API (/docs/api-reference/endpoints/events/events) `GET https://api.stockcontext.com/v2/events` SEC-derived event stream for a stock, newest first. 8-K/8-K/A rows carry the SEC item codes disclosed with a fixed machine-readable label per item (e.g. `2.02` → `results_of_operations`, `5.02` → `officer_director_changes`); non-8-K SEC forms that are event-like (late filings, delisting/deregistration, offerings, M&A/tender/going-private forms, comment-letter releases, and stake events from 13D/G) carry form-derived labels. Labels are the SEC's own disclosure categories — never judgments. Current production is assembled from the cached SEC submissions/event source store and may return an unsupported or unavailable envelope with a reason when the symbol is structurally unsupported or source cache is cold. The source-complete launch plan moves this behind a materialized latest-known-good event store. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `limit` | query | no | Maximum number of events to return, newest first. Defaults to 20; must be between 1 and 50. | | `labels` | query | no | Optional comma-separated classification tokens to filter the stream to (e.g. 'officer_director_changes,late_filing_notice'). Unknown tokens are rejected with 400 (PARAM_INVALID). Useful where routine filings dominate — issuers with structured-note shelf programs (large banks) file 'prospectus_offering' events daily. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T18:33:23Z", "asset_type": "stock", "cache_age_seconds": 0, "events": [ { "accession": "0000320193-26-000011", "date": "2026-04-30", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-26-015711", "date": "2026-04-20", "form": "8-K", "items": [ "5.02" ], "labels": [ "officer_director_changes" ] }, { "accession": "0001140361-26-006577", "date": "2026-02-24", "form": "8-K", "items": [ "5.07", "9.01" ], "labels": [ "shareholder_vote_results", "financial_statements_and_exhibits" ] }, { "accession": "0000320193-26-000005", "date": "2026-01-29", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-26-000199", "date": "2026-01-02", "form": "8-K", "items": [ "5.02" ], "labels": [ "officer_director_changes" ] }, { "accession": "0001140361-25-044561", "date": "2025-12-05", "form": "8-K", "items": [ "5.02" ], "labels": [ "officer_director_changes" ] }, { "accession": "0001354457-25-001138", "date": "2025-11-14", "form": "25-NSE", "items": [], "labels": [ "delisting_notice" ] }, { "accession": "0000320193-25-000077", "date": "2025-10-30", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-25-036313", "date": "2025-09-26", "form": "S-8", "items": [], "labels": [ "employee_plan_registration" ] }, { "accession": "0001140361-25-036310", "date": "2025-09-26", "form": "S-8", "items": [], "labels": [ "employee_plan_registration" ] }, { "accession": "0000320193-25-000071", "date": "2025-07-31", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-25-027340", "date": "2025-07-25", "form": "8-K", "items": [ "5.02" ], "labels": [ "officer_director_changes" ] }, { "accession": "0001140361-25-025275", "date": "2025-07-09", "form": "8-K", "items": [ "5.02" ], "labels": [ "officer_director_changes" ] }, { "accession": "0001354457-25-000451", "date": "2025-05-22", "form": "25-NSE", "items": [], "labels": [ "delisting_notice" ] }, { "accession": "0001140361-25-018400", "date": "2025-05-12", "form": "8-K", "items": [ "8.01", "9.01" ], "labels": [ "other_events", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-25-017543", "date": "2025-05-06", "form": "424B2", "items": [], "labels": [ "prospectus_offering" ] }, { "accession": "0001140361-25-017233", "date": "2025-05-05", "form": "424B2", "items": [], "labels": [ "prospectus_offering" ] }, { "accession": "0000320193-25-000055", "date": "2025-05-01", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] }, { "accession": "0001140361-25-005876", "date": "2025-02-25", "form": "8-K", "items": [ "5.07" ], "labels": [ "shareholder_vote_results" ] }, { "accession": "0000320193-25-000007", "date": "2025-01-30", "form": "8-K", "items": [ "2.02", "9.01" ], "labels": [ "results_of_operations", "financial_statements_and_exhibits" ] } ], "freshness": "end_of_day", "market_status": "open", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/events?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/events", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/events?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Dividend data API (/docs/api-reference/endpoints/financials/dividends) `GET https://api.stockcontext.com/v2/dividends` Current yield, annual rate, frequency, next estimated ex-date, growth streaks, and a dividend-health grade. Non-payers return `status: "non_payer"` with `{available: false, reason}` objects, never a fake 0% yield. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Payer (AAPL) ```json { "data": { "as_of": "2026-06-11T20:00:00Z", "cache_age_seconds": 26488, "currency": "USD", "current": { "annual_rate": 1.08, "annual_rate_forward_estimated": 1.08, "frequency": "quarterly", "last_ex_date": "2026-05-11", "last_payment_amount": 0.27, "next_ex_date_estimated": "2026-08-10", "next_payment_amount_estimated": 0.27, "yield_basis": "forward_annualized", "yield_pct": 0.37 }, "dividend_health": { "payout_components": { "ebitda_coverage": 10.31, "fcf_payout_ratio_pct": 12.04, "net_debt_to_ebitda": 0.1, "ocf_coverage": 9.02, "payout_ratio_pct": 12.69 }, "track_record": { "consecutive_increase_years_within_5y_window": 5, "cuts_in_5y_window": 0, "never_cut_in_5y_window": true } }, "freshness": "end_of_day", "growth": { "consecutive_years_increased_within_5y_window": 5, "last_increase_date": "2026-05-11" }, "history_summary": { "ex_date_first": "2021-08-06", "total_payments": 20 }, "market_status": "open", "status": "payer", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ### Non-payer (TSLA) ```json { "data": { "as_of": "2026-06-11T20:00:00Z", "cache_age_seconds": 26488, "currency": "USD", "current": { "annual_rate": 0, "frequency": "none", "yield_pct": { "available": false, "reason": "no_dividend_payer" } }, "dividend_health": { "reason": "no_dividend_payments", "supported": false }, "freshness": "end_of_day", "growth": { "reason": "no_dividend_history", "supported": false }, "history_summary": { "ex_date_first": { "available": false, "reason": "no_dividend_history" }, "total_payments": 0 }, "market_status": "open", "status": "non_payer", "symbol": "TSLA" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/dividends?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/dividends", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/dividends?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock earnings API (/docs/api-reference/endpoints/financials/earnings) `GET https://api.stockcontext.com/v2/earnings` The last report (EPS and revenue with year-over-year change and a trend reading), an estimated next report date, and a per-quarter history. Stocks only. ETFs return `freshness: "unsupported"` because ETFs do not report earnings. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) History trimmed to 3 quarters for display. ```json { "data": { "as_of": "2026-06-12T21:43:21Z", "cache_age_seconds": 0, "company_guidance": { "available": false, "reason": "no_guidance_detected" }, "currency": "USD", "freshness": "end_of_day", "history": [ { "eps_actual": 2.84, "eps_yoy_pct": 18.33, "filing_date": "2026-01-30", "fiscal_period": "Q1", "fiscal_year": 2026, "period_end": "2025-12-27", "revenue_actual": 143756000000, "revenue_yoy_pct": 15.65 }, { "eps_actual": 1.85, "eps_yoy_pct": 90.72, "filing_date": "2025-10-31", "fiscal_period": "Q4", "fiscal_year": 2025, "period_end": "2025-09-27", "revenue_actual": 102466000000, "revenue_yoy_pct": 7.94 }, { "eps_actual": 1.57, "eps_yoy_pct": 12.14, "filing_date": "2025-08-01", "fiscal_period": "Q3", "fiscal_year": 2025, "period_end": "2025-06-28", "revenue_actual": 94036000000, "revenue_yoy_pct": 9.63 } ], "last": { "eps_actual": 2.01, "eps_yoy_pct": 21.82, "filing_date": "2026-05-01", "fiscal_period": "Q2", "fiscal_year": 2026, "period_end": "2026-03-28", "revenue_actual": 111184000000, "revenue_yoy_pct": 16.6, "vs_trend": { "eps_baseline_4q_avg": 1.98, "eps_vs_trailing_4q_avg_pct": 1.64, "eps_yoy_pct": 21.82, "revenue_baseline_4q_avg": 108904250000, "revenue_vs_trailing_4q_avg_pct": 2.09, "revenue_yoy_pct": 16.6 } }, "market_status": "closed", "next_estimated_date": "2026-07-31", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY): unsupported ```json { "data": { "as_of": "2026-06-12T14:12:22Z", "asset_type": "etf", "cache_age_seconds": 0, "freshness": "unsupported", "market_status": "open", "reason": "etf_no_earnings_reports", "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/earnings?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/earnings", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/earnings?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # SEC facts API (point-in-time) (/docs/api-reference/endpoints/financials/facts) `GET https://api.stockcontext.com/v2/facts` Generic period grid over the curated SEC fact catalog. Request 1–10 concept names for up to 12 quarterly or annual periods; unknown concepts return under `unavailable_concepts` rather than failing the whole request. `as_of` serves the point-in-time view using only filings known by that date, and `provenance=full` adds accession, form, filed date, tag, and restatement flags. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `concepts` | query | yes | Comma-separated cataloged concept names (1-10), e.g. 'revenue,net_income,total_assets'. The vocabulary is the same snake_case set /v2/fundamentals serves plus the statement-detail catalog; unknown names come back under unavailable_concepts rather than failing the request. | | `periods` | query | no | Period window: 'q' quarters or 'y' fiscal years, n between 1 and 12. Defaults to 8q. | | `as_of` | query | no | Point-in-time date (YYYY-MM-DD): serve the catalog as it was KNOWN on that date — only facts filed on or before it. Restatements filed later are excluded; no look-ahead. | | `provenance` | query | no | 'full' adds per-cell citations (accession, form, filed, exact XBRL tag, restated flag). | ## Response examples (captured from production) ### Max-shape fact grid (AAPL) Max-shape fixture: 10 concepts x 12 periods with full provenance. ```json { "data": { "as_of": "2026-06-05T20:15:00Z", "asset_type": "stock", "cache_age_seconds": 0, "cadence": "quarterly", "facts": { "columns": [ "period_end", "revenue", "cost_of_revenue", "operating_income", "net_income", "eps_diluted", "total_assets", "total_equity", "operating_cash_flow", "capex", "stock_based_comp" ], "currency_unit": "USD", "order": "newest_first", "provenance": [ { "accession": "0000320193-26-000013", "column": "revenue", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-26-000013", "column": "cost_of_revenue", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-26-000013", "column": "operating_income", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-26-000013", "column": "net_income", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "NetIncomeLoss" }, { "accession": "0000320193-26-000013", "column": "eps_diluted", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-26-000013", "column": "total_assets", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "StockholdersEquity" }, { "accession": "0000320193-26-000013", "column": "operating_cash_flow", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-26-000013", "column": "capex", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-26-000013", "column": "stock_based_comp", "filed": "2026-05-01", "form": "10-Q", "period": "2026-03-28", "tag": "AllocatedShareBasedCompensationExpense" }, { "accession": "0000320193-26-000006", "column": "revenue", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-26-000006", "column": "cost_of_revenue", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-26-000006", "column": "operating_income", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-26-000006", "column": "net_income", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "NetIncomeLoss" }, { "accession": "0000320193-26-000006", "column": "eps_diluted", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-26-000006", "column": "total_assets", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2025-12-27", "tag": "StockholdersEquity" }, { "accession": "0000320193-26-000006", "column": "operating_cash_flow", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-26-000006", "column": "capex", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-26-000006", "column": "stock_based_comp", "filed": "2026-01-30", "form": "10-Q", "period": "2025-12-27", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-25-000079", "column": "revenue", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000079", "column": "cost_of_revenue", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000079", "column": "operating_income", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "net_income", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "eps_diluted", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "NetIncomeLoss" }, { "accession": "0000320193-26-000013", "column": "total_assets", "filed": "2026-05-01", "form": "10-Q", "period": "2025-09-27", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2025-09-27", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000079", "column": "operating_cash_flow", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000079", "column": "capex", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000079", "column": "stock_based_comp", "filed": "2025-10-31", "form": "10-K", "period": "2025-09-27", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-25-000073", "column": "revenue", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000073", "column": "cost_of_revenue", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000073", "column": "operating_income", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000073", "column": "net_income", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000073", "column": "eps_diluted", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-25-000073", "column": "total_assets", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "Assets" }, { "accession": "0000320193-25-000073", "column": "total_equity", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000073", "column": "operating_cash_flow", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000073", "column": "capex", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000073", "column": "stock_based_comp", "filed": "2025-08-01", "form": "10-Q", "period": "2025-06-28", "tag": "AllocatedShareBasedCompensationExpense" }, { "accession": "0000320193-26-000013", "column": "revenue", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-26-000013", "column": "cost_of_revenue", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-26-000013", "column": "operating_income", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-26-000013", "column": "net_income", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "NetIncomeLoss" }, { "accession": "0000320193-26-000013", "column": "eps_diluted", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-25-000057", "column": "total_assets", "filed": "2025-05-02", "form": "10-Q", "period": "2025-03-29", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "StockholdersEquity" }, { "accession": "0000320193-26-000013", "column": "operating_cash_flow", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-26-000013", "column": "capex", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-26-000013", "column": "stock_based_comp", "filed": "2026-05-01", "form": "10-Q", "period": "2025-03-29", "tag": "AllocatedShareBasedCompensationExpense" }, { "accession": "0000320193-26-000006", "column": "revenue", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-26-000006", "column": "cost_of_revenue", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-26-000006", "column": "operating_income", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-26-000006", "column": "net_income", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "NetIncomeLoss" }, { "accession": "0000320193-26-000006", "column": "eps_diluted", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-25-000008", "column": "total_assets", "filed": "2025-01-31", "form": "10-Q", "period": "2024-12-28", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2024-12-28", "tag": "StockholdersEquity" }, { "accession": "0000320193-26-000006", "column": "operating_cash_flow", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-26-000006", "column": "capex", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-26-000006", "column": "stock_based_comp", "filed": "2026-01-30", "form": "10-Q", "period": "2024-12-28", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-25-000079", "column": "revenue", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000079", "column": "cost_of_revenue", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000079", "column": "operating_income", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "net_income", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "eps_diluted", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "total_assets", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "Assets" }, { "accession": "0000320193-26-000013", "column": "total_equity", "filed": "2026-05-01", "form": "10-Q", "period": "2024-09-28", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000079", "column": "operating_cash_flow", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000079", "column": "capex", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000079", "column": "stock_based_comp", "filed": "2025-10-31", "form": "10-K", "period": "2024-09-28", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-25-000073", "column": "revenue", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000073", "column": "cost_of_revenue", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000073", "column": "operating_income", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000073", "column": "net_income", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000073", "column": "eps_diluted", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-24-000081", "column": "total_assets", "filed": "2024-08-02", "form": "10-Q", "period": "2024-06-29", "tag": "Assets" }, { "accession": "0000320193-25-000073", "column": "total_equity", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000073", "column": "operating_cash_flow", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000073", "column": "capex", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000073", "column": "stock_based_comp", "filed": "2025-08-01", "form": "10-Q", "period": "2024-06-29", "tag": "AllocatedShareBasedCompensationExpense" }, { "accession": "0000320193-25-000057", "column": "revenue", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000057", "column": "cost_of_revenue", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000057", "column": "operating_income", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000057", "column": "net_income", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000057", "column": "eps_diluted", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-24-000069", "column": "total_assets", "filed": "2024-05-03", "form": "10-Q", "period": "2024-03-30", "tag": "Assets" }, { "accession": "0000320193-25-000073", "column": "total_equity", "filed": "2025-08-01", "form": "10-Q", "period": "2024-03-30", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000057", "column": "operating_cash_flow", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000057", "column": "capex", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000057", "column": "stock_based_comp", "filed": "2025-05-02", "form": "10-Q", "period": "2024-03-30", "tag": "AllocatedShareBasedCompensationExpense" }, { "accession": "0000320193-25-000008", "column": "revenue", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000008", "column": "cost_of_revenue", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000008", "column": "operating_income", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000008", "column": "net_income", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000008", "column": "eps_diluted", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-24-000006", "column": "total_assets", "filed": "2024-02-02", "form": "10-Q", "period": "2023-12-30", "tag": "Assets" }, { "accession": "0000320193-25-000057", "column": "total_equity", "filed": "2025-05-02", "form": "10-Q", "period": "2023-12-30", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000008", "column": "operating_cash_flow", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000008", "column": "capex", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000008", "column": "stock_based_comp", "filed": "2025-01-31", "form": "10-Q", "period": "2023-12-30", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-25-000079", "column": "revenue", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-25-000079", "column": "cost_of_revenue", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-25-000079", "column": "operating_income", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "net_income", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "NetIncomeLoss" }, { "accession": "0000320193-25-000079", "column": "eps_diluted", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "NetIncomeLoss" }, { "accession": "0000320193-24-000123", "column": "total_assets", "filed": "2024-11-01", "form": "10-K", "period": "2023-09-30", "tag": "Assets" }, { "accession": "0000320193-25-000079", "column": "total_equity", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "StockholdersEquity" }, { "accession": "0000320193-25-000079", "column": "operating_cash_flow", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-25-000079", "column": "capex", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-25-000079", "column": "stock_based_comp", "filed": "2025-10-31", "form": "10-K", "period": "2023-09-30", "tag": "ShareBasedCompensation" }, { "accession": "0000320193-24-000081", "column": "revenue", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "RevenueFromContractWithCustomerExcludingAssessedTax" }, { "accession": "0000320193-24-000081", "column": "cost_of_revenue", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "CostOfGoodsAndServicesSold" }, { "accession": "0000320193-24-000081", "column": "operating_income", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "OperatingIncomeLoss" }, { "accession": "0000320193-24-000081", "column": "net_income", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "NetIncomeLoss" }, { "accession": "0000320193-24-000081", "column": "eps_diluted", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "EarningsPerShareDiluted" }, { "accession": "0000320193-23-000077", "column": "total_assets", "filed": "2023-08-04", "form": "10-Q", "period": "2023-07-01", "tag": "Assets" }, { "accession": "0000320193-24-000081", "column": "total_equity", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "StockholdersEquity" }, { "accession": "0000320193-24-000081", "column": "operating_cash_flow", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "NetCashProvidedByUsedInOperatingActivities" }, { "accession": "0000320193-24-000081", "column": "capex", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "PaymentsToAcquirePropertyPlantAndEquipment" }, { "accession": "0000320193-24-000081", "column": "stock_based_comp", "filed": "2024-08-02", "form": "10-Q", "period": "2023-07-01", "tag": "AllocatedShareBasedCompensationExpense" } ], "rows": [ [ "2026-03-28", 111184000000, 56403000000, 35885000000, 29578000000, 2.01, 371082000000, 106491000000, 28702000000, -1971000000, 3528000000 ], [ "2025-12-27", 143756000000, 74525000000, 50852000000, 42097000000, 2.84, 379297000000, 88190000000, 53925000000, -2373000000, 3594000000 ], [ "2025-09-27", 102466000000, 54125000000, 32427000000, 27466000000, 1.85, 359241000000, 73733000000, 29728000000, -3242000000, 3183000000 ], [ "2025-06-28", 94036000000, 50318000000, 28202000000, 23434000000, 1.57, 331495000000, 65830000000, 27867000000, -3462000000, 3168000000 ], [ "2025-03-29", 95359000000, 50492000000, 29589000000, 24780000000, 1.65, 331233000000, 66796000000, 23952000000, -3071000000, 3226000000 ], [ "2024-12-28", 124300000000, 66025000000, 42832000000, 36330000000, 2.4, 344085000000, 66758000000, 29935000000, -2940000000, 3286000000 ], [ "2024-09-28", 94930000000, 51051000000, 29591000000, 14736000000, 0.97, 364980000000, 56950000000, 26811000000, -2908000000, 2858000000 ], [ "2024-06-29", 85777000000, 46099000000, 25352000000, 21448000000, 1.4, 331612000000, 66708000000, 28858000000, -2151000000, 2869000000 ], [ "2024-03-30", 90753000000, 48482000000, 27900000000, 23636000000, 1.53, 337411000000, 74194000000, 22690000000, -1996000000, 2964000000 ], [ "2023-12-30", 119575000000, 64720000000, 40373000000, 33916000000, 2.18, 353514000000, 74100000000, 39895000000, -2392000000, 2997000000 ], [ "2023-09-30", 89498000000, 49071000000, 26969000000, 22956000000, 1.46, 352583000000, 62146000000, 21598000000, -2163000000, 2625000000 ], [ "2023-07-01", 81797000000, 45384000000, 22998000000, 19881000000, 1.26, 335038000000, 60274000000, 26380000000, -2093000000, 2617000000 ] ], "unavailable": [] }, "freshness": "end_of_day", "market_status": "closed", "symbol": "AAPL", "unavailable_concepts": [] }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/facts?symbol=AAPL&concepts=revenue,net_income,eps_diluted&periods=8q" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/facts", params={"symbol": "AAPL", "concepts": "revenue,net_income,eps_diluted", "periods": "8q"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/facts?symbol=AAPL&concepts=revenue,net_income,eps_diluted&periods=8q", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Company fundamentals API (/docs/api-reference/endpoints/financials/fundamentals) `GET https://api.stockcontext.com/v2/fundamentals` Curated statement rows (income statement, balance sheet, cash flow) for the last 8 quarters or last 5 fiscal years, plus a TTM block with margins and returns on capital. Decision-shaped rows, not raw filings. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `period` | query | no | Reporting cadence. Allowed values: quarter (last 8 quarters) or annual (last 5 fiscal years). Omit for the default view: quarter for quarterly filers, and annual for annual-only foreign private issuers (reporting_cadence 'annual' — TSM/SAP class file no quarterly statements). An explicit period=quarter for such a filer returns a quarterly_view notice pointing here. | | `blocks` | query | no | Optional comma-separated subset of statement blocks to include: income_statement, balance_sheet, cash_flow, ttm, trajectory, forensics, statement_detail (the extended filing-detail field catalog - opt-in only, never in the default response), bank_metrics (bank-native economics - present only for banks). Omit for the full response (default, unchanged). Use this to pay for only the block you need — e.g. blocks=ttm is a fraction of the full payload's tokens. | ## Response examples (captured from production) ### Quarterly (AAPL) Statement arrays trimmed to 2 periods for display; live responses return up to 8. ```json { "data": { "as_of": "2026-06-12T14:10:29Z", "balance_sheet": [ { "cash_and_equivalents": 45572000000, "current_assets": 144114000000, "current_liabilities": 134641000000, "debt_borrowings": 84711000000, "finance_lease_liabilities": null, "fiscal_period": "Q2", "fiscal_year": 2026, "intangibles": 25797000000, "inventory": 6747000000, "long_term_debt": 74404000000, "long_term_investments": 78088000000, "net_debt": 16204000000, "operating_lease_liabilities": null, "period_end": "2026-03-28", "ppe_net": 50116000000, "provenance": { "accession": "0000320193-26-000013", "filed": "2026-05-01", "form": "10-Q" }, "ratios": { "current_ratio": 1.07, "debt_to_assets": 0.23, "debt_to_equity": 0.8, "interest_coverage": { "available": false, "reason": "zero_interest_expense" }, "net_debt_to_ebitda": 0.1 }, "receivables": 30339000000, "retained_earnings": 12359000000, "shares_outstanding_cover_page": 14667688000, "short_term_investments": 22935000000, "tangible_book_value": 80694000000, "total_assets": 371082000000, "total_debt": 84711000000, "total_equity": 106491000000, "total_liabilities": 264591000000 }, { "cash_and_equivalents": 45317000000, "current_assets": 158104000000, "current_liabilities": 162367000000, "debt_borrowings": 90509000000, "finance_lease_liabilities": null, "fiscal_period": "Q1", "fiscal_year": 2026, "intangibles": null, "inventory": 5875000000, "long_term_debt": 76685000000, "long_term_investments": 77888000000, "net_debt": 23602000000, "operating_lease_liabilities": null, "period_end": "2025-12-27", "ppe_net": 50159000000, "provenance": { "accession": "0000320193-26-000006", "filed": "2026-01-30", "form": "10-Q" }, "ratios": { "current_ratio": 0.97, "debt_to_assets": 0.24, "debt_to_equity": 1.03, "interest_coverage": { "available": false, "reason": "zero_interest_expense" }, "net_debt_to_ebitda": 0.15 }, "receivables": 39921000000, "retained_earnings": -2177000000, "shares_outstanding_cover_page": 14702703000, "short_term_investments": 21590000000, "tangible_book_value": 88190000000, "total_assets": 379297000000, "total_debt": 90509000000, "total_equity": 88190000000, "total_liabilities": 291107000000 } ], "cache_age_seconds": 113, "cash_flow": [ { "acquisitions": null, "buybacks": -12618000000, "capex": -1971000000, "dividends_paid": -3822000000, "fcf_margin_pct": 24.04, "fcf_yoy_pct": 28.02, "financing_cash_flow": -22279000000, "fiscal_period": "Q2", "fiscal_year": 2026, "free_cash_flow": 26731000000, "investing_cash_flow": -6168000000, "ocf_yoy_pct": 19.83, "operating_cash_flow": 28702000000, "period_end": "2026-03-28", "provenance": { "accession": "0000320193-26-000013", "filed": "2026-05-01", "form": "10-Q" }, "stock_based_comp": 3528000000, "working_capital_change": 13736000000 }, { "acquisitions": null, "buybacks": -27623000000, "capex": -2373000000, "dividends_paid": -3921000000, "fcf_margin_pct": 35.86, "fcf_yoy_pct": 90.97, "financing_cash_flow": -39656000000, "fiscal_period": "Q1", "fiscal_year": 2026, "free_cash_flow": 51552000000, "investing_cash_flow": -4886000000, "ocf_yoy_pct": 80.14, "operating_cash_flow": 53925000000, "period_end": "2025-12-27", "provenance": { "accession": "0000320193-26-000006", "filed": "2026-01-30", "form": "10-Q" }, "stock_based_comp": 3594000000, "working_capital_change": 13411000000 } ], "currency": "USD", "forensics": { "accruals_ratio_pct": -5.02, "cash_conversion": 1.14, "cash_conversion_cycle_days": { "value": -50.3, "yoy_delta": 3.6 }, "days_inventory": { "value": 9.8, "yoy_delta": -0.8 }, "days_payables": { "value": 83.5, "yoy_delta": -3.3 }, "days_sales_outstanding": { "value": 23.4, "yoy_delta": 1.1 }, "diluted_shares_cagr_3y_pct": -2.42, "diluted_shares_cagr_5y_pct": -2.75, "fcf_per_share_cagr_3y_pct": 12.55, "revenue_per_share_cagr_3y_pct": 8.05, "sbc_pct_ocf": 9.61, "sbc_pct_revenue": 2.98 }, "freshness": "end_of_day", "income_statement": [ { "cost_of_revenue": 56403000000, "depreciation_amortization": 3439000000, "ebitda": 39324000000, "eps_basic": 2.02, "eps_diluted": 2.01, "fiscal_period": "Q2", "fiscal_year": 2026, "gross_profit": 54781000000, "growth": { "eps_yoy_pct": 21.82, "net_income_yoy_pct": 19.36, "revenue_qoq_pct": -22.66, "revenue_yoy_pct": 16.6, "rnd_yoy_pct": 33.56 }, "income_tax": 6255000000, "interest_coverage": { "available": false, "reason": "zero_interest_expense" }, "interest_expense": null, "investment": { "capex_intensity_3y_avg_pct": 2.5, "capex_intensity_pct": 1.77, "rnd_intensity_pct": 10.27 }, "margins": { "ebitda_margin_pct": 35.37, "gross_margin_pct": 49.27, "net_margin_pct": 26.6, "operating_margin_pct": 32.28 }, "net_income": 29578000000, "operating_expense": 18896000000, "operating_income": 35885000000, "period_end": "2026-03-28", "pretax_income": 35833000000, "provenance": { "accession": "0000320193-26-000013", "filed": "2026-05-01", "form": "10-Q" }, "quality": { "piotroski_f_score": 8 }, "revenue": 111184000000, "rnd_expense": 11419000000, "sga_expense": 7477000000, "shares_basic": 14673278000, "shares_diluted": 14725873000 }, { "cost_of_revenue": 74525000000, "depreciation_amortization": 3214000000, "ebitda": 54066000000, "eps_basic": 2.85, "eps_diluted": 2.84, "fiscal_period": "Q1", "fiscal_year": 2026, "gross_profit": 69231000000, "growth": { "eps_yoy_pct": 18.33, "net_income_yoy_pct": 15.87, "revenue_qoq_pct": 40.3, "revenue_yoy_pct": 15.65, "rnd_yoy_pct": 31.68 }, "income_tax": 8905000000, "interest_coverage": { "available": false, "reason": "zero_interest_expense" }, "interest_expense": null, "investment": { "capex_intensity_3y_avg_pct": 2.01, "capex_intensity_pct": 1.65, "rnd_intensity_pct": 7.57 }, "margins": { "ebitda_margin_pct": 37.61, "gross_margin_pct": 48.16, "net_margin_pct": 29.28, "operating_margin_pct": 35.37 }, "net_income": 42097000000, "operating_expense": 18379000000, "operating_income": 50852000000, "period_end": "2025-12-27", "pretax_income": 51002000000, "provenance": { "accession": "0000320193-26-000006", "filed": "2026-01-30", "form": "10-Q" }, "quality": { "piotroski_f_score": 9 }, "revenue": 143756000000, "rnd_expense": 10887000000, "sga_expense": 7492000000, "shares_basic": 14748158000, "shares_diluted": 14810356000 } ], "market_status": "open", "period": "quarter", "reporting_cadence": "quarterly", "statements_source": "sec_edgar", "symbol": "AAPL", "trajectory": { "eps_growth": { "last_quarters_yoy": [ -1.87, -0.16, 0.3, 22.86, 25.56, 28.82 ], "latest_yoy_pct": 28.82, "n_quarters": 6, "trend": "accelerating" }, "fcf_margin": { "last_quarters": [ 21.72, 21.9, 25.95, 25.85, 35.86, 24.04 ], "latest_pct": 24.04, "n_quarters": 6, "trend": "increasing" }, "gross_margin": { "last_quarters": [ 46.88, 47.05, 46.49, 47.18, 48.16, 49.27 ], "latest_pct": 49.27, "n_quarters": 6, "trend": "stable" }, "revenue_growth": { "last_quarters_yoy": [ 2.61, 4.91, 5.97, 6.43, 10.07, 12.76 ], "latest_yoy_pct": 12.76, "n_quarters": 6, "trend": "accelerating" }, "roic": { "fy_values": [ 65.21, 61.43, 70.01, 71.49 ], "latest_pct": 71.49, "n_years": 4, "trend": "increasing" } }, "ttm": { "eps_diluted_ttm": 8.27, "flags": [], "net_income_ttm_usd": 122575000000, "net_profit_margin_pct": 27.15, "revenue_ttm_usd": 451442000000, "roa_pct": 34.02, "roe_pct": 146.69, "roic_pct": 81.47 } }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/fundamentals?symbol=AAPL&period=quarter" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/fundamentals", params={"symbol": "AAPL", "period": "quarter"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/fundamentals?symbol=AAPL&period=quarter", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Segmented financials API (/docs/api-reference/endpoints/financials/segments) `GET https://api.stockcontext.com/v2/segments` Private beta: this route remains callable for explicitly enabled/private integrations, but it is not part of the public analyst core or default MCP/agent guidance. Multi-period segment grid from issuer SEC XBRL: business and geographic members, revenue, operating profit, accessions, filing dates, segment recast flags, and G-SEG revenue reconciliation. The route is cache-only: a missing bootstrap returns `segments: {available:false, reason:'segments_store_not_built'}` instead of fetching EDGAR on the request path. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Segment grid (AAPL) Rows trimmed for display. ```json { "data": { "as_of": "2026-06-05T20:15:00Z", "asset_type": "stock", "cache_age_seconds": 0, "freshness": "end_of_day", "market_status": "closed", "segments": { "built_at": "2026-06-05T20:15:00Z", "captured_accessions": [ "0000320193-26-000057" ], "columns": [ "axis", "member", "label", "period", "period_end", "filed", "accession", "revenue", "operating_profit" ], "currency_unit": "USD", "g_seg": [ { "accession": "0000320193-26-000057", "consolidated_revenue": 94040000000, "period": "quarter", "period_end": "2026-03-28", "segment_revenue_sum": 94040000000, "status": "in_band" } ], "member_sets": { "0000320193-26-000057": [ "aapl:AmericasSegmentMember", "aapl:EuropeSegmentMember" ] }, "order": "newest_first", "profit_concept": { "0000320193-26-000057": "OperatingIncomeLoss" }, "rows": [ [ "business", "aapl:AmericasSegmentMember", "Americas", "quarter", "2026-03-28", "2026-05-02", "0000320193-26-000057", 44590000000, 19640000000 ], [ "business", "aapl:EuropeSegmentMember", "Europe", "quarter", "2026-03-28", "2026-05-02", "0000320193-26-000057", 24840000000, 10010000000 ], [ "geography", "country:US", "United States", "quarter", "2026-03-28", "2026-05-02", "0000320193-26-000057", 40120000000, null ] ], "segment_recast": false, "source": "sec_filing_xbrl", "unavailable": [ { "accession": "0000320193-26-000057", "axis": "geography", "column": "operating_profit", "period": "quarter", "period_end": "2026-03-28", "reason": "segment_profit_not_disclosed" } ] }, "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/segments?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/segments", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/segments?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Historical stock price API (30 years) (/docs/api-reference/endpoints/market-context/history) `GET https://api.stockcontext.com/v2/history` Selected time series in columnar `{columns, rows}` form: price, volume, RSI, financial ratios, or dividends, at daily through annual cadence. Daily price history covers up to 30 years (the max cap), bounded by source availability per symbol, so check `coverage.start`/`coverage.end`. Request only the series you need, because payloads grow fast. `period` is accepted as a legacy alias for `range`. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `series` | query | no | Comma-separated list of history series. Allowed values: close, volume, rsi_14, pe_ttm, ps_ttm, ev_ebitda_ttm, roic_pct, fcf_margin_pct, gross_margin_pct, dividend. daily/weekly support close, volume, rsi_14, pe_ttm, ps_ttm, ev_ebitda_ttm; quarterly/annual support roic_pct, fcf_margin_pct, gross_margin_pct; per-payment supports dividend. The valuation series (pe_ttm, ps_ttm, ev_ebitda_ttm) are derived from SEC EDGAR filings with point-in-time TTM windows joined on each filing's date. The dividend series requires cadence=per-payment explicitly. | | `range` | query | no | Historical lookback window. Allowed values: 1m, 1mo, 3m, 3mo, 6m, 6mo, 1y, 2y, 5y, 10y, 30y, ytd, max. Long `mo` forms normalize to short month forms; `max` is capped at 30y. | | `period` | query | no | Legacy alias for `range` (back-compat). | | `cadence` | query | no | Row cadence. Allowed values: daily, weekly, quarterly, annual, per-payment. Required as per-payment when series=dividend. | | `format` | query | no | Response format: 'rows' (default) or 'compact'. | ## Response examples (captured from production) ### Daily closes, 1 month (AAPL) Rows trimmed to 6 for display; this request returns ~22 rows live. ```json { "data": { "as_of": "2026-06-11T23:59:59Z", "cache_age_seconds": 0, "cadence": "daily", "columns": [ "date", "close" ], "coverage": { "end": "2026-06-11", "start": "2026-05-12" }, "freshness": "end_of_day", "market_status": "open", "period": "1m", "rows": [ [ "2026-05-12", 294.8 ], [ "2026-05-13", 298.87 ], [ "2026-05-14", 298.21 ], [ "2026-05-15", 300.23 ], [ "2026-05-18", 297.84 ], [ "2026-05-19", 298.97 ] ], "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/history?symbol=AAPL&series=close&range=1m" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/history", params={"symbol": "AAPL", "series": "close", "range": "1m"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/history?symbol=AAPL&series=close&range=1m", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Stock price action API (/docs/api-reference/endpoints/market-context/price-action) `GET https://api.stockcontext.com/v2/price-action` How the price is behaving: volume vs 30-day and sector averages, 30/90-day extremes, gap and streak context, biggest single-day moves, and max drawdowns over 90 days, 1 year, and 5 years. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `blocks` | query | no | Opt-in blocks. Only 'fails_to_deliver' is defined: the SEC's semi-monthly CNS fails series with explicit as_of_settlement_date and data_lag_days stamps. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T14:12:18Z", "cache_age_seconds": 0, "drawdowns": { "max_1y": { "pct": -13.8, "peak_date": "2025-12-02", "trough_date": "2026-01-20" }, "max_5y": { "pct": -33.36, "peak_date": "2024-12-26", "trough_date": "2025-04-08" }, "max_90d": { "pct": -7.82, "peak_date": "2026-06-02", "trough_date": "2026-06-09" } }, "freshness": "intraday", "gaps": { "latest_open_vs_prior_close_pct": 0.35, "unfilled_gaps_30d": 0 }, "market_status": "open", "recent_extremes": { "high_30d": { "date": "2026-06-08", "value": 317.4 }, "high_90d": { "date": "2026-06-08", "value": 317.4 }, "low_30d": { "date": "2026-06-10", "value": 287.38 }, "low_90d": { "date": "2026-03-30", "value": 245.28 } }, "streaks": { "biggest_down_day_1y_pct": -5, "biggest_down_day_30d_pct": -3.64, "biggest_up_day_1y_pct": 5.09, "biggest_up_day_30d_pct": 2.9, "consecutive_days": 0, "direction": "none" }, "symbol": "AAPL", "trend_context": { "consecutive_higher_highs": 1, "consecutive_lower_lows": 0, "pct_days_above_sma50_90d": 58 }, "volume": { "avg_30d": 52324678, "latest": 42275511, "ratio_to_avg": 0.81, "ratio_to_sector_avg": 3.02 } }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/price-action?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/price-action", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/price-action?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Technical indicators API (RSI, MACD, SMA) (/docs/api-reference/endpoints/market-context/technicals) `GET https://api.stockcontext.com/v2/technicals` Daily and weekly RSI with zone readings, stochastic, MACD, SMA/EMA relationships with golden-cross state, ADX trend strength, OBV, Bollinger bands, ATR, realized volatility, beta, and relative strength vs SPY and sector. One call, computed server-side. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Stock (AAPL) ```json { "data": { "as_of": "2026-06-12T14:12:18Z", "cache_age_seconds": 0, "freshness": "intraday", "market_status": "open", "momentum": { "macd": { "above_signal": false, "histogram": -3.34, "signal_line": 6.68, "value": 3.34 }, "rsi_14": 48.17, "rsi_14_weekly": 58.29, "rsi_14_weekly_zone": "neutral", "rsi_zone": "neutral", "stochastic_14_3_3": { "d": 21.03, "k": 16.94, "zone": "oversold" } }, "moving_averages": { "distance_from_52w_high_pct": -8.04, "ema_20": 299.99, "golden_cross_active": true, "sma_200": 266.14, "sma_50": 284.65, "vs_sma_200_pct": 9.67, "vs_sma_50_pct": 2.54 }, "relative_strength": { "vs_sector_pct_1y": -4, "vs_spy_pct_1m": 0.34, "vs_spy_pct_1y": 25.23, "vs_spy_pct_3m": 6.6 }, "symbol": "AAPL", "trend_strength": { "adx_14": 37.2, "adx_14_weekly": 22.78, "obv": 3672923156, "obv_trend_30d": "up", "trending": true, "trending_weekly": false }, "volatility": { "atr_14": 7.3, "beta_1y_weekly": 1.09, "bollinger_20": { "lower": 290.2, "middle": 304.24, "percent_b": 0.06, "upper": 318.27 }, "realized_vol_30d_annualized_pct": 23.34 } }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/technicals?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/technicals", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/technicals?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Filing diff API (risk factors & MD&A) (/docs/api-reference/endpoints/sec-filings/filing-diff) `GET https://api.stockcontext.com/v2/filings/{accession}/diff` Paragraph-level diff of one filing's `risk_factors` and `mda` sections against the same issuer's prior filing of the same form family (10-K vs prior 10-K, 10-Q vs prior 10-Q). Only changed paragraphs ship — `added`/`modified` entries carry this filing's new paragraph, `removed` entries carry the prior filing's paragraph — never both full texts. `{changed: [], total_changes: 0}` means the section is textually identical to the prior filing; per-section `{available: false, reason}` envelopes explain a missing prior or an unextractable section, and 8-K/other forms return `diff_not_supported_for_form`. Paragraph texts cap at 1200 characters (`trimmed`), sections at 80 entries (`truncated`, with the full `total_changes` count). Diffs are computed once per accession and cached long — filings are immutable; the first call on a cold accession can be slow. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `accession` | path | yes | SEC accession number in dashed form (e.g. 0000320193-25-000001). | ## Response examples (captured from production) ### Risk factors & MD&A vs prior 10-K ```json { "data": { "accession": "0000320193-25-000079", "as_of": "2026-06-12T00:42:41Z", "cache_age_seconds": 48585, "form": "10-K", "freshness": "end_of_day", "market_status": "open", "prior_accession": "0000320193-24-000123", "prior_filing_date": "2024-11-01", "sections": { "mda": { "changed": [ { "change": "modified", "index": 0, "section": "mda", "text": "Item 7. Management’s Discussion and Analysis of Financial Condition and Results of Operations The following discussion should be read in conjunction with the consolidated financial statements and accompanying notes included in Part II, Item 8 of this Form 10-K. This Item generally discusses 2025 and 2024 items and year-to-year comparisons between 2025 and 2024. Discussions of 2023 items and year-to-year comparisons between 2024 and 2023 are not included, and can be found in “Management’s Discussion and Analysis of Financial Condition and Results of Operations” in Part II, Item 7 of the Company’s Annual Report on Form 10-K for the fiscal year ended September 28, 2024. Product, Service and Software Announcements The Company announces new product, service and software offerings at various times during the year. Significant announcements during fiscal year 2025 included the following: First Quarter 2025: •MacBook Pro •Mac mini •iMac •iPad mini Second Quarter 2025: •iPhone 16e •iPad Air •iPad •MacBook Air •Mac Studio Third Quarter 2025: •iOS 26, macOS Tahoe 26, iPadOS 26, watchOS 26, visionOS 26 and tvOS 26 Fourth Quarter 2025: •iPhone 17, iPhone Air, iPhone 17 Pro and iPhone 17 Pro Max", "trimmed": true }, { "change": "modified", "index": 1, "section": "mda", "text": "Tariffs and Other Measures Beginning in the second quarter of 2025, new U.S. Tariffs were announced, including additional tariffs on imports from China, India, Japan, South Korea, Taiwan, Vietnam and the EU, among others. In response, several countries have imposed, or threatened to impose, reciprocal tariffs on imports from the U.S. and other retaliatory measures. Various modifications to the U.S. Tariffs have been announced and further changes could be made in the future, which may include additional sector-based tariffs or other measures. For example, the U.S. Department of Commerce has initiated an investigation under Section 232 of the Trade Expansion Act of 1962, as amended, into, among other things, imports of semiconductors, semiconductor manufacturing equipment, and their derivative products, including downstream products that contain semiconductors. Tariffs and other measures that are applied to the Company’s products or their components can have a material adverse impact on the Company’s business, results of operations and financial condition, including impacting the Company’s supply chain, the availability of rare earths and other raw materials and components, pricing a", "trimmed": true }, { "change": "modified", "index": 2, "section": "mda", "text": "2025 Change 2024 Change 2023 Americas $178,353 7 % $167,045 3 % $162,560 Europe 111,032 10 % 101,328 7 % 94,294 Greater China 64,377 (4) % 66,952 (8) % 72,559 Japan 28,703 15 % 25,052 3 % 24,257 Rest of Asia Pacific 33,696 10 % 30,658 4 % 29,615 Total net sales $416,161 6 % $391,035 2 % $383,285 Americas Americas net sales increased during 2025 compared to 2024 primarily due to higher net sales of iPhone and Services. The weakness in foreign currencies relative to the U.S. dollar had an unfavorable year-over-year impact on Americas net sales during 2025. Europe Europe net sales increased during 2025 compared to 2024 primarily due to higher net sales of Services, iPhone and Mac. Greater China Greater China net sales decreased during 2025 compared to 2024 primarily due to lower net sales of iPhone, partially offset by higher net sales of Mac. Japan Japan net sales increased during 2025 compared to 2024 primarily due to higher net sales of iPhone, Services and iPad. Rest of Asia Pacific Rest of Asia Pacific net sales increased during 2025 compared to 2024 primarily due to higher net sales of iPhone, Services and Mac. Apple Inc. | 2025 Form 10-K | 22" }, { "change": "modified", "index": 3, "section": "mda", "text": "Products and Services Performance The following table shows net sales by category for 2025, 2024 and 2023 (dollars in millions):" }, { "change": "modified", "index": 4, "section": "mda", "text": "2025 Change 2024 Change 2023 iPhone $209,586 4 % $201,183 — % $200,583 Mac 33,708 12 % 29,984 2 % 29,357 iPad 28,023 5 % 26,694 (6) % 28,300 Wearables, Home and Accessories 35,686 (4) % 37,005 (7) % 39,845 Services (1) 109,158 14 % 96,169 13 % 85,200 Total net sales $416,161 6 % $391,035 2 % $383,285 (1)Services net sales include amortization of the deferred value of services bundled in the sales price of certain products. iPhone iPhone net sales increased during 2025 compared to 2024 due to higher net sales of Pro models. Mac Mac net sales increased during 2025 compared to 2024 primarily due to higher net sales of laptops and desktops. iPad iPad net sales increased during 2025 compared to 2024 primarily due to higher net sales of iPad Air, iPad mini and iPad, partially offset by lower net sales of iPad Pro. Wearables, Home and Accessories Wearables, Home and Accessories net sales decreased during 2025 compared to 2024 primarily due to lower net sales of Accessories and Wearables. Services Services net sales increased during 2025 compared to 2024 primarily due to higher net sales from advertising, the App Store and cloud services. Apple Inc. | 2025 Form 10-K | 23" }, { "change": "modified", "index": 5, "section": "mda", "text": "Gross Margin Products and Services gross margin and gross margin percentage for 2025, 2024 and 2023 were as follows (dollars in millions):" }, { "change": "modified", "index": 6, "section": "mda", "text": "2025 2024 2023 Gross margin: Products $112,887 $109,633 $108,803 Services 82,314 71,050 60,345 Total gross margin $195,201 $180,683 $169,148" }, { "change": "modified", "index": 7, "section": "mda", "text": "Gross margin percentage: Products 36.8 % 37.2 % 36.5 % Services 75.4 % 73.9 % 70.8 % Total gross margin percentage 46.9 % 46.2 % 44.1 % Products Gross Margin Products gross margin increased during 2025 compared to 2024 primarily due to favorable costs and a different mix of products, partially offset by tariff costs. Products gross margin percentage decreased during 2025 compared to 2024 primarily due to a different mix of products and tariff costs, partially offset by other favorable costs. Services Gross Margin Services gross margin increased during 2025 compared to 2024 primarily due to higher Services net sales and a different mix of services. Services gross margin percentage increased during 2025 compared to 2024 primarily due to a different mix of services, partially offset by higher costs. The Company’s future gross margins can be impacted by a variety of factors, as discussed in Part I, Item 1A of this Form 10-K under the heading “Risk Factors.” As a result, the Company believes, in general, gross margins will be subject to volatility and downward pressure. Operating Expenses Operating expenses for 2025, 2024 and 2023 were as follows (dollars in millions):" }, { "change": "modified", "index": 8, "section": "mda", "text": "2025 Change 2024 Change 2023 Research and development $34,550 10 % $31,370 5 % $29,915 Percentage of total net sales 8 % 8 % 8 % Selling, general and administrative $27,601 6 % $26,097 5 % $24,932 Percentage of total net sales 7 % 7 % 7 % Total operating expenses $62,151 8 % $57,467 5 % $54,847 Percentage of total net sales 15 % 15 % 14 % Research and Development The growth in R&D expense during 2025 compared to 2024 was primarily driven by increases in headcount-related expenses and infrastructure-related costs. Selling, General and Administrative The growth in selling, general and administrative expense during 2025 compared to 2024 was primarily driven by increases in headcount-related expenses and variable selling expenses. Apple Inc. | 2025 Form 10-K | 24" }, { "change": "modified", "index": 9, "section": "mda", "text": "Provision for Income Taxes Provision for income taxes, effective tax rate and statutory federal income tax rate for 2025, 2024 and 2023 were as follows (dollars in millions):" }, { "change": "modified", "index": 10, "section": "mda", "text": "2025 2024 2023 Provision for income taxes $20,719 $29,749 $16,741 Effective tax rate 15.6 % 24.1 % 14.7 % Statutory federal income tax rate 21 % 21 % 21 % The Company’s effective tax rate for 2025 was lower than the statutory federal income tax rate primarily due to a lower effective tax rate on foreign earnings, including the impact of changes in unrecognized tax benefits, the impact of the U.S. federal R&D credit, and tax benefits from share-based compensation, partially offset by a change in valuation allowance and state income taxes. The Company’s effective tax rate for 2025 was lower compared to 2024 due to a $10.7 billion year-over-year decrease in the provision for income taxes related to the State Aid Decision (refer to Note 7, “Income Taxes” in the Notes to Consolidated Financial Statements in Part II, Item 8 of this Form 10-K) and the impact of changes in unrecognized tax benefits, partially offset by a change in valuation allowance and a higher effective tax rate on foreign earnings. Liquidity and Capital Resources The Company believes its balances of cash, cash equivalents and marketable securities, which totaled $132.4 billion as of September 27, 2025, along with cash", "trimmed": true }, { "change": "modified", "index": 11, "section": "mda", "text": "Capital Return Program In addition to its contractual cash requirements, the Company has an authorized share repurchase program. The program does not obligate the Company to acquire a minimum amount of shares. As of September 27, 2025, the Company’s quarterly cash dividend was $0.26 per share. The Company intends to increase its dividend on an annual basis, subject to declaration by the Board. In May 2025, the Company announced a new share repurchase program of up to $100 billion and raised its quarterly dividend from $0.25 to $0.26 per share beginning in May 2025. During 2025, the Company repurchased $89.3 billion of its common stock and paid dividends and dividend equivalents of $15.4 billion. Recent Accounting Pronouncements Internal-Use Software In September 2025, the Financial Accounting Standards Board (“FASB”) issued Accounting Standards Update (“ASU”) No. 2025-06, Intangibles—Goodwill and Other—Internal-Use Software (Subtopic 350-40): Targeted Improvements to the Accounting for Internal-Use Software (“ASU 2025-06”), which modernizes the accounting for internal-use software. ASU 2025-06 removes all references to software development stages and requires capitalization of soft", "trimmed": true }, { "change": "modified", "index": 12, "section": "mda", "text": "Legal and Other Contingencies The Company is subject to various legal proceedings and claims that arise in the ordinary course of business, the outcomes of which are inherently uncertain. The Company records a liability when it is probable a loss has been incurred and the amount is reasonably estimable, the determination of which requires significant judgment. Resolution of legal matters in a manner inconsistent with management’s expectations could have a material impact on the Company’s financial condition and operating results." } ], "total_changes": 13 }, "risk_factors": { "changed": [ { "change": "modified", "index": 0, "section": "risk_factors", "text": "Item 1A. Risk Factors The following summarizes factors that could have a material adverse effect on the Company’s business, reputation, results of operations, financial condition and stock price. The Company may not be able to accurately predict, control or mitigate these risks. Statements in this section are based on the Company’s beliefs and opinions regarding matters that could materially adversely affect the Company in the future and are not representations as to whether such matters have or have not occurred previously. The risks and uncertainties described below are not exhaustive and should not be considered a complete statement of all potential risks or uncertainties that the Company faces or may face in the future. This section should be read in conjunction with Part II, Item 7, “Management’s Discussion and Analysis of Financial Condition and Results of Operations” and the consolidated financial statements and accompanying notes in Part II, Item 8, “Financial Statements and Supplementary Data” of this Form 10-K. Macroeconomic and Industry Risks The Company’s operations and performance depend significantly on global and regional economic conditions and adverse economic cond", "trimmed": true }, { "change": "modified", "index": 1, "section": "risk_factors", "text": "The Company’s business can be impacted by political events, trade and other international disputes, geopolitical tensions, conflict, terrorism, natural disasters, public health issues, industrial accidents and other business interruptions. Political events, trade and other international disputes, geopolitical tensions, conflict, terrorism, natural disasters, public health issues, industrial accidents and other business interruptions can have a material adverse effect on the Company and its customers, employees, suppliers, contract manufacturers, logistics providers, distributors, cellular network carriers and other channel partners. The Company has a large, global business with sales outside the U.S. representing a majority of the Company’s total net sales, and the Company believes that it generally benefits from growth in international trade. A significant majority of the Company’s manufacturing is performed in whole or in part by outsourcing partners located primarily in China mainland, India, Japan, South Korea, Taiwan and Vietnam, in addition to sourcing from partners and facilities located in the U.S. Restrictions on international trade, such as tariffs and other controls on i", "trimmed": true }, { "change": "modified", "index": 2, "section": "risk_factors", "text": "Following any interruption to its business, the Company can require substantial recovery time, incur significant expenditures to resume operations, and lose significant sales. Because the Company relies on single or limited sources for the supply and manufacture of many critical components, a business interruption affecting such sources would exacerbate any negative consequences to the Company. While the Company maintains insurance coverage for certain types of losses, such insurance coverage may be insufficient to cover all losses that may arise. Any of the foregoing can materially adversely affect the Company’s business, results of operations, financial condition and stock price. Global markets for the Company’s products and services are highly competitive and subject to rapid technological change, and the Company may be unable to compete effectively in these markets. The Company’s products and services are offered in highly competitive global markets. These markets are characterized by aggressive price competition, downward pressure on gross margins, continual improvement in product performance, and price sensitivity on the part of consumers and businesses. These markets are fur", "trimmed": true }, { "change": "modified", "index": 3, "section": "risk_factors", "text": "Business Risks To remain competitive and stimulate customer demand, the Company must successfully manage frequent introductions and transitions of products and services. Due to the highly volatile and competitive nature of the markets and industries in which the Company competes, the Company must continually introduce new products, services and technologies, enhance existing products and services, effectively stimulate customer demand for new and upgraded products and services, navigate global regulatory requirements and barriers to market access, and successfully manage the transition to these new and upgraded products and services. The success of new product and service introductions depends on a number of factors, including the Company’s ability to recruit and retain highly skilled personnel to execute on its strategic initiatives, and the timely and successful development and market acceptance of new products, services and technologies. Success also relies on the Company’s ability to manage the risks associated with new technologies and production ramp-up issues, the effective integration of third-party services and technologies into the Company’s products and services, the ava", "trimmed": true }, { "change": "modified", "index": 4, "section": "risk_factors", "text": "The Company’s products and services may be affected from time to time by design and manufacturing defects that could materially adversely affect the Company’s business and result in harm to the Company’s reputation. The Company offers complex hardware and software products and services that can be affected by design and manufacturing defects. Sophisticated operating system software and applications, such as those offered by the Company, often have issues that can unexpectedly interfere with the intended operation of hardware or software products and services. Defects can also exist in components and products the Company purchases from third parties. Component defects could make the Company’s products unsafe and create a risk of environmental or property damage and personal injury. These risks may increase as the Company’s products are introduced into specialized applications, including health. In addition, the Company’s service offerings can have quality issues and from time to time experience outages, service slowdowns or errors. As a result, from time to time the Company’s services have not performed as anticipated and may not meet customer expectations. The introduction of new a", "trimmed": true }, { "change": "modified", "index": 5, "section": "risk_factors", "text": "The Company’s future performance depends in part on support from third-party software developers. The Company believes decisions by customers to purchase its hardware products depend in part on the availability of third-party software applications and services. Third-party developers may discontinue the development and maintenance of software applications and services for the Company’s products. If third-party software applications and services cease to be developed and maintained for the Company’s products, customers may choose not to buy the Company’s products, adversely impacting the Company’s business, results of operations, financial condition and stock price. The Company believes that third-party developer support depends on the perceived benefits of creating software and services for the Company’s products compared to competitors’ platforms, such as Android for smartphones and tablets, Windows for personal computers and tablets, and PlayStation, Nintendo and Xbox for gaming platforms. This analysis may be based on factors such as the market position of the Company and its products, the anticipated revenue that may be generated, expected future growth of product sales, and th", "trimmed": true }, { "change": "modified", "index": 6, "section": "risk_factors", "text": "The Company depends on the performance of carriers and other resellers. The Company distributes its products and certain of its services through cellular network carriers and other resellers, many of which distribute products and services from competitors. Resellers offer financing, installment payment plans or subsidies for users’ purchases of devices, and such plans may be discontinued or modified any time. The Company has invested and will continue to invest in programs to enhance reseller sales, including staffing selected resellers’ stores with Company employees and contractors, improving product placement displays, and developing and making digital marketing assets available to resellers. These programs can require a substantial investment while not assuring return or incremental sales. For example, the purchasing preferences and behaviors of consumers may change, the financial condition of resellers could weaken, resellers could stop distributing the Company’s products, or uncertainty regarding demand for some or all of the Company’s products could cause resellers to reduce their ordering and marketing of the Company’s products, all of which could materially adversely impact", "trimmed": true }, { "change": "modified", "index": 7, "section": "risk_factors", "text": "As with all companies, the security the Company has implemented may not be sufficient for all eventualities and are vulnerable to hacking, ransomware attacks, employee error, malfeasance, system error, faulty password management or other irregularities. For example, third parties can fraudulently induce the Company’s or its suppliers’ and other third parties’ employees or customers into disclosing usernames, passwords or other sensitive information, which can, in turn, be used for unauthorized access to the Company’s or such suppliers’ or third parties’ systems and services. To help protect customers and the Company, the Company deploys and makes available technologies like multifactor authentication, monitors its services and systems for unusual activity and may freeze accounts under suspicious circumstances, which, among other things, can result in the delay or loss of customer orders or impede customer access to the Company’s products and services. While the Company maintains insurance coverage that is intended to address certain aspects of data security risks, such insurance coverage may be insufficient to cover all losses or all types of claims that may arise. Investment in ne", "trimmed": true }, { "change": "modified", "index": 8, "section": "risk_factors", "text": "The outcome of litigation or government investigations is inherently uncertain. If one or more legal matters were resolved against the Company or an indemnified third party in a reporting period for amounts above management’s expectations, the Company’s results of operations, financial condition and stock price for that reporting period could be materially adversely affected. Further, such an outcome can result in significant monetary damages, disgorgement of revenue or profits, remedial corporate measures or injunctive relief against the Company. Adverse resolution of legal matters has from time to time required, and can in the future require, the Company to change its business practices. It can also limit the Company’s ability to enjoin others from using, or to derive value from, its intellectual property rights, and to develop, manufacture, use, import or offer for sale certain products and services, all of which could materially adversely affect the Company’s business, reputation, results of operations, financial condition and stock price. While the Company maintains insurance coverage for certain types of claims, such insurance coverage may be insufficient to cover all losses", "trimmed": true }, { "change": "modified", "index": 9, "section": "risk_factors", "text": "The technology industry, including, in some instances, the Company, is subject to intense media, political and regulatory scrutiny, which exposes the Company to increasing regulation, government investigations, legal actions and penalties. From time to time, the Company has made changes to its business, including actions taken in response to litigation, competition, market conditions and legal and regulatory requirements. The Company expects to make further business changes in the future. For example, in the U.S., the Company has implemented changes to how developers communicate with consumers within apps on the U.S. storefront of the iOS and iPadOS App Store regarding alternative purchasing mechanisms and is currently subject to a court order preventing it from imposing any commission or fee on certain purchases that consumers make. Globally, several jurisdictions have adopted, or may in the future adopt, competition-related laws and regulations imposing wide-ranging obligations on technology companies and significant limitations on businesses, including the Company. For example, the Company has implemented changes to iOS, iPadOS, the App Store and Safari® in the EU as it seeks to", "trimmed": true }, { "change": "modified", "index": 10, "section": "risk_factors", "text": "The Company’s business is subject to a variety of U.S. and international laws, rules, policies and other obligations regarding the collection, use, protection and transfer of personal data. The Company is subject to an increasing number of federal, state and international laws relating to the collection, use, retention, protection and transfer of various types of personal data. In many cases, these laws apply not only to third-party transactions, but also restrict transfers of personal data among the Company and its international subsidiaries. Several jurisdictions have passed laws in this area, and additional jurisdictions are considering imposing additional restrictions or have laws that are pending. These laws continue to develop and may be inconsistent from jurisdiction to jurisdiction. Complying with emerging and changing requirements causes the Company to incur substantial costs and has required and may in the future require the Company to change its business practices. Noncompliance could result in significant penalties or legal liability. The Company makes statements about its use and disclosure of personal data through its privacy policy, information provided on its websit", "trimmed": true }, { "change": "modified", "index": 11, "section": "risk_factors", "text": "Conversely, a strengthening of foreign currencies relative to the U.S. dollar, while generally beneficial to the Company’s foreign currency–denominated sales and earnings, could cause the Company to reduce international pricing or incur losses on its foreign currency derivative instruments, thereby limiting the benefit. Additionally, strengthening of foreign currencies may increase the Company’s cost of product components denominated in those currencies, thus adversely affecting gross margins. The Company uses derivative instruments, such as foreign currency forward and option contracts, to hedge certain exposures to fluctuations in foreign exchange rates. The use of such hedging activities may not be effective to offset any, or more than a portion, of the adverse financial effects of unfavorable movements in foreign exchange rates over the limited time the hedges are in place. The Company is exposed to credit risk and fluctuations in the values of its investment portfolio. The Company’s investments can be negatively affected by changes in liquidity, credit deterioration, financial results, market and economic conditions, political risk, sovereign risk, interest rate fluctuations o", "trimmed": true }, { "change": "modified", "index": 12, "section": "risk_factors", "text": "General Risks The price of the Company’s stock is subject to volatility. The Company’s stock has experienced substantial price volatility in the past and may continue to do so in the future. Additionally, the Company, the technology industry and the stock market as a whole have, from time to time, experienced extreme stock price and volume fluctuations that have affected stock prices in ways that may have been unrelated to these companies’ operating performance. Price volatility may cause the average price at which the Company repurchases its stock in a given period to exceed the stock’s price at a given point in time. The Company believes the price of its stock should reflect expectations of future growth and profitability. The Company also believes the price of its stock should reflect expectations that its cash dividend will continue at current levels or grow, and that its current share repurchase program will be fully consummated. Future dividends are subject to declaration by the Company’s Board of Directors (“Board”), and the Company’s share repurchase program does not obligate it to acquire any specific number of shares. If the Company fails to meet expectations related to f", "trimmed": true } ], "total_changes": 13 } } }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/filings/0000320193-25-000079/diff" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/filings/0000320193-25-000079/diff", headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/filings/0000320193-25-000079/diff", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # SEC filing text API (risk factors, MD&A) (/docs/api-reference/endpoints/sec-filings/filing-section) `GET https://api.stockcontext.com/v2/filings/{accession}/section/{name}` Full text of one section of a filing: `risk_factors`, `mda`, `business`, and peers for 10-K/10-Q, or `press_release` for 8-K. `char_count` arrives before you commit to reading: sections run from a few KB to several hundred KB, so fetch only what you will actually read. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `accession` | path | yes | SEC accession number in dashed form (e.g. 0000320193-25-000001). | | `name` | path | yes | Filing section name. Allowed values: business, risk_factors, mda, quantitative_qualitative_disclosures, controls_and_procedures, press_release. | | `symbol` | query | no | Optional cross-check. When supplied, the filing's resolved filer must match this symbol or the request fails with `PARAM_INVALID`. | ## Response examples (captured from production) ### Risk factors (10-K) `text` truncated for display; live responses return the full section. ```json { "data": { "accession": "0000320193-25-000079", "as_of": "2026-06-12T14:12:26Z", "cache_age_seconds": 0, "char_count": 68069, "form": "10-K", "freshness": "end_of_day", "market_status": "open", "section": "risk_factors", "symbol": "AAPL", "text": "Item 1A. Risk Factors\nThe following summarizes factors that could have a material adverse effect on the Company’s business, reputation, results of operations, financial condition and stock price. The Company may not be able to accurately predict, control or mitigate these risks. Statements in this section are based on the Company’s beliefs and opinions regarding matters that could materially adversely affect the C … [truncated for docs]" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/filings/0000320193-25-000079/section/risk_factors" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/filings/0000320193-25-000079/section/risk_factors", headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/filings/0000320193-25-000079/section/risk_factors", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # SEC filing lookup API (/docs/api-reference/endpoints/sec-filings/filing) `GET https://api.stockcontext.com/v2/filings/{accession}` Metadata for one filing by accession number. The shape follows the form type: 10-K/10-Q responses include a `sections` index (what you can fetch as text), 8-K responses include `items` and press-release availability, and Form 4 responses carry the filing facts only. A well-formed accession that matches no filing returns a 200 with `symbol_status: {resolved: false}`. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `accession` | path | yes | SEC accession number in dashed form (e.g. 0000320193-25-000001). | | `symbol` | query | no | Optional cross-check. When supplied, the filing's resolved filer must match this symbol or the request fails with `PARAM_INVALID`. | ## Response examples (captured from production) ### 10-K ```json { "data": { "accession": "0000320193-25-000079", "as_of": "2026-06-12T14:12:26Z", "cache_age_seconds": 604394, "filing_date": "2025-10-31", "form": "10-K", "freshness": "end_of_day", "market_status": "open", "period_of_report": "2025-09-27", "sections": [ { "char_count": 16004, "name": "business" }, { "char_count": 68069, "name": "risk_factors" }, { "char_count": 21009, "name": "mda" } ], "symbol": "AAPL", "url": "https://www.sec.gov/Archives/edgar/data/320193/0000320193-25-000079-index.html" }, "schema_version": "2026-06-17.6" } ``` ### 8-K ```json { "data": { "accession": "0000320193-26-000011", "as_of": "2026-06-12T14:12:26Z", "cache_age_seconds": 604382, "filing_date": "2026-04-30", "form": "8-K", "freshness": "end_of_day", "items": [ { "code": "2.02", "text": "Results of Operations and Financial Condition" }, { "code": "9.01", "text": "Financial Statements and Exhibits" } ], "market_status": "open", "period_of_report": "2026-04-30", "press_release": { "char_count": 27021 }, "symbol": "AAPL", "url": "https://www.sec.gov/Archives/edgar/data/320193/0000320193-26-000011-index.html" }, "schema_version": "2026-06-17.6" } ``` ### Form 4 ```json { "data": { "accession": "0001140361-26-023363", "as_of": "2026-06-12T14:12:27Z", "cache_age_seconds": 604378, "filing_date": "2026-05-29", "form": "4", "freshness": "end_of_day", "market_status": "open", "period_of_report": "2026-05-27", "symbol": "AAPL", "url": "https://www.sec.gov/Archives/edgar/data/320193/0001140361-26-023363-index.html" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/filings/0000320193-25-000079" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/filings/0000320193-25-000079", headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/filings/0000320193-25-000079", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # SEC filings API (EDGAR) (/docs/api-reference/endpoints/sec-filings/filings-list) `GET https://api.stockcontext.com/v2/filings` Recent SEC filings for a stock, newest first, filtered by form type (default 10-K, 10-Q, 8-K). Each row carries the `accession` you pass to the filing metadata and section endpoints. First calls for a company can be slow while EDGAR data is cold; repeat calls are cached. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `form` | query | no | Comma-separated SEC form-type filter. Allowed values: 10-K, 10-Q, 8-K, 4, UPLOAD, CORRESP. Defaults to 10-K,10-Q,8-K when omitted. | | `limit` | query | no | Maximum number of filing rows to return, newest first. Defaults to 20; must be between 1 and 100. Requests above 100 are rejected with PARAM_INVALID: narrow `form` or the date window to scope larger histories. | ## Response examples (captured from production) ### Recent filings (AAPL) Trimmed to 3 rows for display. ```json { "data": { "as_of": "2026-06-12T14:12:25Z", "cache_age_seconds": 0, "filings": [ { "accession": "0000320193-26-000013", "filing_date": "2026-05-01", "form": "10-Q", "items": [], "items_text": [], "period_of_report": "2026-03-28", "sections": [], "size_bytes": 5809851, "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000013/aapl-20260328.htm" }, { "accession": "0000320193-26-000011", "filing_date": "2026-04-30", "form": "8-K", "items": [ "2.02", "9.01" ], "items_text": [ "Results of Operations and Financial Condition", "Financial Statements and Exhibits" ], "period_of_report": "2026-04-30", "size_bytes": 412047, "url": "https://www.sec.gov/Archives/edgar/data/320193/000032019326000011/aapl-20260430.htm" }, { "accession": "0001140361-26-015711", "filing_date": "2026-04-20", "form": "8-K", "items": [ "5.02" ], "items_text": [ "Departure of Directors or Certain Officers; Election of Directors" ], "period_of_report": "2026-04-17", "size_bytes": 239761, "url": "https://www.sec.gov/Archives/edgar/data/320193/000114036126015711/ef20071035_8k.htm" } ], "freshness": "end_of_day", "market_status": "open", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/filings?symbol=AAPL&form=10-K,10-Q&limit=5" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/filings", params={"symbol": "AAPL", "form": "10-K,10-Q", "limit": "5"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/filings?symbol=AAPL&form=10-K,10-Q&limit=5", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Governance and compensation API (/docs/api-reference/endpoints/sec-filings/governance) `GET https://api.stockcontext.com/v2/governance` Private beta: this route remains callable for explicitly enabled/private integrations, but it is not part of the public analyst core or default MCP/agent guidance. Proxy-derived governance facts for a stock: SEC ecd pay-versus-performance grid, CEO-to-median-employee pay ratio, and ballot proposal facts. Values come from the issuer's own DEF 14A/14C XBRL where collected; pending stores return explicit unavailable reasons. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### Proxy facts (AAPL) ```json { "data": { "as_of": "2026-06-12T21:43:29Z", "asset_type": "stock", "cache_age_seconds": 0, "freshness": "end_of_day", "governance": { "accession": "0001308179-26-000008", "basis": "def14a_ecd_xbrl", "ceo_pay_ratio": { "ceo_comp_usd": 74294811, "median_employee_comp_usd": 139483, "ratio": 533 }, "coverage_start": "2022-12-16", "filed": "2026-01-08", "form": "DEF 14A", "pay_vs_performance": { "columns": [ "fiscal_year_end", "peo_actually_paid", "neo_avg_actually_paid", "total_shareholder_return", "peer_group_tsr", "net_income" ], "currency_unit": "USD", "order": "newest_first", "rows": [ [ "2025-09-27", 108423733, 34125743, 233.88, 279.51, 112010000000 ], [ "2024-09-28", 168980568, 58633525, 207.59, 206.32, 93736000000 ], [ "2023-09-30", 106643588, 41980664, 155.24, 132.03, 96955000000 ], [ "2022-09-24", 128833021, 41564946, 135.59, 92.83, 99803000000 ], [ "2021-09-25", 311845801, 75307922, 131.69, 136.94, 94680000000 ] ] }, "peo_name": "Mr. Cook", "voting_proposals": [ { "description": "Election of Directors", "number": 1, "proposal_type": "director_election" }, { "description": "Ratification of Appointment of Independent Registered Public Accounting Firm", "number": 2, "proposal_type": "auditor_ratification" }, { "description": "Advisory Vote to Approve Executive Compensation", "number": 3, "proposal_type": "say_on_pay" }, { "description": "Approval of the Apple Inc.", "number": 4, "proposal_type": "company_proposal" }, { "description": "Shareholder Proposal", "number": 5, "proposal_type": "shareholder_proposal" } ] }, "market_status": "closed", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/governance?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/governance", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/governance?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Insider trading API (Form 4) (/docs/api-reference/endpoints/sec-filings/insider) `GET https://api.stockcontext.com/v2/insider` Trailing-90-day Form 4 rollup: buy/sell counts and net value for open-market activity, all-kinds totals, the 10b5-1 planned-sale ratio, and officer/director split, plus the most recent transactions with codes, prices, and remaining stakes. Stocks only. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | | `limit` | query | no | Maximum number of rows to return. Bounded by the endpoint-specific minimum / maximum constraints documented in the parameter schema. | ## Response examples (captured from production) ### Stock (AAPL) Recent transactions trimmed to 3 for display. ```json { "data": { "as_of": "2026-06-12T21:43:21Z", "cache_age_seconds": 0, "freshness": "end_of_day", "intent_to_sell": [ { "accession": "0001921094-26-000555", "approx_sale_date": "2026-05-27", "broker": "Charles Schwab & Co., Inc.", "filed": "2026-05-27", "is_10b5_1_plan": false, "market_value_usd": 15551085, "person_selling": "LEVINSON ARTHUR D", "security_class": "Common", "units_to_be_sold": 50000 }, { "accession": "0001921094-26-000446", "approx_sale_date": "2026-05-06", "broker": "Charles Schwab & Co., Inc.", "filed": "2026-05-06", "is_10b5_1_plan": false, "market_value_usd": 71190164, "person_selling": "LEVINSON ARTHUR D", "security_class": "Common", "units_to_be_sold": 250000 }, { "accession": "0001950047-26-004044", "approx_sale_date": "2026-05-05", "broker": "Morgan Stanley Smith Barney LLC Executive Financial Services", "filed": "2026-05-05", "is_10b5_1_plan": false, "market_value_usd": 12039492.6, "person_selling": "KATHERINE L ADAMS", "security_class": "Common", "units_to_be_sold": 43000 }, { "accession": "0001950047-26-003721", "approx_sale_date": "2026-04-23", "broker": "Morgan Stanley Smith Barney LLC Executive Financial Services", "filed": "2026-04-23", "is_10b5_1_plan": true, "market_value_usd": 419042.78, "person_selling": "KEVAN PAREKH", "security_class": "Common", "units_to_be_sold": 1534 }, { "accession": "0001959173-26-002757", "approx_sale_date": "2026-04-02", "broker": "Fidelity Brokerage Services LLC", "filed": "2026-04-02", "is_10b5_1_plan": true, "market_value_usd": 16512062.21, "person_selling": "COOK TIMOTHY D", "security_class": "Common Stock", "units_to_be_sold": 64949 }, { "accession": "0001969223-26-000420", "approx_sale_date": "2026-04-02", "broker": "UBS Financial Services Inc.", "filed": "2026-04-02", "is_10b5_1_plan": true, "market_value_usd": 7660977.7, "person_selling": "O'BRIEN DEIRDRE", "security_class": "Common", "units_to_be_sold": 30002 } ], "market_status": "closed", "recent_transactions": [ { "accession": "0001140361-26-023363", "filing_date": "2026-05-29", "insider_name": "Arthur D Levinson", "insider_role": "Director", "price_per_share": 311.02, "shares": 50000, "ten_b5_1": { "available": false, "reason": "trading_plan_not_disclosed" }, "transaction_code": "S", "transaction_date": "2026-05-27", "transaction_kind": "open_market_sale", "value_usd": 15551000 }, { "accession": "0001140361-26-023363", "filing_date": "2026-05-29", "insider_name": "Arthur D Levinson", "insider_role": "Director", "price_per_share": { "available": false, "reason": "gift_no_market_price" }, "shares": 65000, "ten_b5_1": { "available": false, "reason": "trading_plan_not_disclosed" }, "transaction_code": "G", "transaction_date": "2026-05-27", "transaction_kind": "gift", "value_usd": { "available": false, "reason": "gift_no_market_price" } }, { "accession": "0001140361-26-020871", "filing_date": "2026-05-12", "insider_name": "Ben Borders", "insider_role": "Principal Accounting Officer", "price_per_share": 290, "shares": 1274, "ten_b5_1": { "available": false, "reason": "trading_plan_not_disclosed" }, "transaction_code": "S", "transaction_date": "2026-05-08", "transaction_kind": "open_market_sale", "value_usd": 369460 } ], "summary_90d": { "all_activity": { "net_shares_all_kinds": 25895, "transactions": 43, "transactions_by_kind": { "derivative_exercise": 22, "gift": 2, "open_market_sale": 13, "tax_withholding": 6 } }, "director_share_pct": 12, "distinct_buyers_30d": 0, "distinct_buyers_90d": 0, "market_activity": { "buy_count": 0, "net_direction": "selling", "net_shares_market_only": -397759, "net_value_usd": -111705105.08, "sell_count": 13 }, "notices": [ "recent_transactions_limited_to_10" ], "officer_share_pct": 42, "ten_b5_1_plan_ratio_pct": { "disclosed_count": 0, "planned_count": 0, "ratio_pct": { "available": false, "reason": "plan_status_not_disclosed" }, "undisclosed_count": 43 }, "unique_insiders": 7 }, "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ### ETF (SPY): unsupported ```json { "data": { "as_of": "2026-06-12T14:12:26Z", "asset_type": "etf", "cache_age_seconds": 0, "freshness": "unsupported", "market_status": "open", "reason": "etf_no_insider_transactions", "symbol": "SPY" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/insider?symbol=AAPL&limit=10" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/insider", params={"symbol": "AAPL", "limit": "10"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/insider?symbol=AAPL&limit=10", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ``` # Ownership API (13D/G and 13F) (/docs/api-reference/endpoints/sec-filings/ownership) `GET https://api.stockcontext.com/v2/ownership` Private beta: this route remains callable for explicitly enabled/private integrations, but it is not part of the public analyst core or default MCP/agent guidance. Two SEC ownership lenses for a stock: beneficial ownership from Schedule 13D/G filings and institutional 13F positioning for the latest collected quarter. Rows preserve filing accessions and as-filed basis; unavailable blocks carry reason tokens while backfills are pending. Access: Starter and Builder plans. ## Parameters | Name | In | Required | Description | | --- | --- | --- | --- | | `symbol` | query | yes | Ticker symbol. Case-insensitive; share-class dots are normalized to dashes (BRK.B == BRK-B). Returns SYMBOL_UNKNOWN when the symbol is outside the supported universe. | ## Response examples (captured from production) ### 13D/G stake example ```json { "data": { "as_of": "2026-06-12T21:43:29Z", "asset_type": "stock", "beneficial_ownership": { "basis": "schedule_13d_13g", "coverage_perimeter": { "note": "stake_filings_before_structured_era_not_collected", "structured_era_start": "2024-12-18" }, "coverage_start": "2024-12-18", "history": [ { "accession": "0001536588-26-000009", "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "holder_name": "Melinda Bradley", "is_exit": false, "pct_of_class": 8.4, "schedule": "13D", "superseded": false }, { "accession": "0001536588-26-000009", "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "holder_name": "Adam Bradley", "is_exit": false, "pct_of_class": 9.4, "schedule": "13D", "superseded": false }, { "accession": "0001536588-26-000009", "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "holder_name": "AJB Capital, LLC", "is_exit": false, "pct_of_class": 7.6, "schedule": "13D", "superseded": false }, { "accession": "0001536588-26-000009", "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "holder_cik": "0001536588", "holder_name": "AJB Investment Fund II, LP", "is_exit": false, "pct_of_class": 7.6, "schedule": "13D", "superseded": false }, { "accession": "0001140361-26-021966", "event_date": "2026-05-11", "filed": "2026-05-18", "form": "SCHEDULE 13D", "holder_name": "Melinda Bradley", "is_exit": false, "pct_of_class": 7.5, "schedule": "13D", "superseded": true }, { "accession": "0001140361-26-021966", "event_date": "2026-05-11", "filed": "2026-05-18", "form": "SCHEDULE 13D", "holder_name": "Adam Bradley", "is_exit": false, "pct_of_class": 8.2, "schedule": "13D", "superseded": true }, { "accession": "0001140361-26-021966", "event_date": "2026-05-11", "filed": "2026-05-18", "form": "SCHEDULE 13D", "holder_name": "AJB Capital, LLC", "is_exit": false, "pct_of_class": 6.9, "schedule": "13D", "superseded": true }, { "accession": "0001140361-26-021966", "event_date": "2026-05-11", "filed": "2026-05-18", "form": "SCHEDULE 13D", "holder_cik": "0001536588", "holder_name": "AJB Investment Fund II, LP", "is_exit": false, "pct_of_class": 6.9, "schedule": "13D", "superseded": true }, { "accession": "0001398344-25-020564", "event_date": "2025-09-30", "filed": "2025-11-10", "form": "SCHEDULE 13G/A", "holder_name": "Comprehensive Financial Planning, Inc.", "is_exit": true, "pct_of_class": 2.92, "schedule": "13G", "superseded": false }, { "accession": "0001398344-25-001490", "event_date": "2024-12-31", "filed": "2025-01-31", "form": "SCHEDULE 13G/A", "holder_name": "Comprehensive Financial Planning, Inc.", "is_exit": false, "pct_of_class": 7.83, "schedule": "13G", "superseded": true }, { "accession": "0001673359-25-000005", "event_date": "2024-12-31", "filed": "2025-01-29", "form": "SCHEDULE 13G/A", "holder_name": "Fort Nelson Partners, LP", "is_exit": false, "pct_of_class": 6.2, "schedule": "13G", "superseded": false } ], "holders": [ { "accession": "0001536588-26-000009", "aggregate_shares": 329647, "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "name": "Adam Bradley", "pct_of_class": 9.4, "person_type": "IN", "schedule": "13D" }, { "accession": "0001536588-26-000009", "aggregate_shares": 296362, "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "name": "Melinda Bradley", "pct_of_class": 8.4, "person_type": "IN", "schedule": "13D" }, { "accession": "0001536588-26-000009", "aggregate_shares": 267768, "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "name": "AJB Capital, LLC", "pct_of_class": 7.6, "person_type": "HC", "schedule": "13D" }, { "accession": "0001536588-26-000009", "aggregate_shares": 267768, "cik": "0001536588", "event_date": "2026-05-11", "filed": "2026-06-12", "form": "SCHEDULE 13D/A", "name": "AJB Investment Fund II, LP", "pct_of_class": 7.6, "person_type": "PN", "schedule": "13D" }, { "accession": "0001673359-25-000005", "aggregate_shares": 217053, "event_date": "2024-12-31", "filed": "2025-01-29", "form": "SCHEDULE 13G/A", "name": "Fort Nelson Partners, LP", "pct_of_class": 6.2, "person_type": "PN", "schedule": "13G" } ], "unparsed_filings": [] }, "cache_age_seconds": 0, "freshness": "end_of_day", "institutional_13f": { "available": false, "reason": "institutional_13f_not_yet_collected" }, "market_status": "closed", "symbol": "JCTC" }, "schema_version": "2026-06-17.6" } ``` ### 13F positioning (AAPL) ```json { "data": { "as_of": "2026-06-12T22:00:39Z", "asset_type": "stock", "beneficial_ownership": { "basis": "schedule_13d_13g", "coverage_perimeter": { "note": "stake_filings_before_structured_era_not_collected", "structured_era_start": "2024-12-18" }, "coverage_start": "2024-12-18", "history": [ { "accession": "0002100119-26-000139", "event_date": "2026-03-31", "filed": "2026-04-29", "form": "SCHEDULE 13G", "holder_name": "Vanguard Capital Management", "is_exit": false, "pct_of_class": 7.48, "schedule": "13G", "superseded": false }, { "accession": "0000102909-26-000630", "event_date": "2026-03-13", "filed": "2026-03-26", "form": "SCHEDULE 13G/A", "holder_name": "The Vanguard Group", "is_exit": true, "pct_of_class": 0, "schedule": "13G", "superseded": false }, { "accession": "0000932471-25-000778", "event_date": "2025-06-30", "filed": "2025-07-29", "form": "SCHEDULE 13G/A", "holder_name": "The Vanguard Group", "is_exit": false, "pct_of_class": 9.47, "schedule": "13G", "superseded": true } ], "holders": [ { "accession": "0002100119-26-000139", "aggregate_shares": 1099168953, "event_date": "2026-03-31", "filed": "2026-04-29", "form": "SCHEDULE 13G", "name": "Vanguard Capital Management", "pct_of_class": 7.48, "person_type": "IA", "schedule": "13G" } ], "unparsed_filings": [] }, "cache_age_seconds": 44, "freshness": "end_of_day", "institutional_13f": { "as_of_quarter_end": "2026-03-31", "concentration_hhi": 0.0412, "filed_through": "2026-05-29", "institution_count": 6013, "qoq": { "institution_count_delta": 118, "prior_quarter_end": "2025-12-31", "total_value_delta_pct": -7.33 }, "restated": true, "top_holders": [ { "cik": "2012383", "implied_price_usd": 253.79, "manager": "BlackRock, Inc.", "pct_of_13f_total": 12.84, "shares": 1144695425, "value_usd": 290512251859 }, { "cik": "2100119", "implied_price_usd": 253.7899, "manager": "VANGUARD CAPITAL MANAGEMENT LLC", "pct_of_13f_total": 10.7, "shares": 953847648, "value_usd": 242076924860 }, { "cik": "93751", "implied_price_usd": 253.79, "manager": "STATE STREET CORP", "pct_of_13f_total": 6.75, "shares": 602341409, "value_usd": 152868226190 }, { "cik": "1214717", "implied_price_usd": 253.1206, "manager": "GEODE CAPITAL MANAGEMENT, LLC", "pct_of_13f_total": 4.12, "shares": 368616954, "value_usd": 93304547364 }, { "cik": "2100121", "implied_price_usd": 253.79, "manager": "VANGUARD PORTFOLIO MANAGEMENT LLC", "pct_of_13f_total": 3.72, "shares": 331437055, "value_usd": 84115410189 }, { "cik": "315066", "implied_price_usd": 253.79, "manager": "FMR LLC", "pct_of_13f_total": 3.45, "shares": 307442040, "value_usd": 78025715113 }, { "cik": "895421", "implied_price_usd": 253.79, "manager": "MORGAN STANLEY", "pct_of_13f_total": 2.74, "shares": 244474722, "value_usd": 62045240497 }, { "cik": "19617", "implied_price_usd": 246.63, "manager": "JPMORGAN CHASE & CO", "pct_of_13f_total": 2.52, "shares": 231622141, "value_usd": 57124969118 }, { "cik": "1067983", "implied_price_usd": 253.79, "manager": "Berkshire Hathaway Inc", "pct_of_13f_total": 2.56, "shares": 227917808, "value_usd": 57843260493 }, { "cik": "73124", "implied_price_usd": 253.79, "manager": "NORTHERN TRUST CORP", "pct_of_13f_total": 1.79, "shares": 159265625, "value_usd": 40420022970 }, { "cik": "70858", "implied_price_usd": 253.79, "manager": "BANK OF AMERICA CORP /DE/", "pct_of_13f_total": 1.35, "shares": 120727964, "value_usd": 30639549998 }, { "cik": "764068", "implied_price_usd": 253.79, "manager": "Legal & General Group Plc", "pct_of_13f_total": 1.21, "shares": 108127380, "value_usd": 27441647769 }, { "cik": "886982", "implied_price_usd": 253.79, "manager": "GOLDMAN SACHS GROUP INC", "pct_of_13f_total": 1.2, "shares": 106585393, "value_usd": 27050306935 }, { "cik": "861177", "implied_price_usd": 253.79, "manager": "UBS AM, a distinct business unit of UBS ASSET MANAGEMENT AMERICAS LLC", "pct_of_13f_total": 1.18, "shares": 105510753, "value_usd": 26777574003 }, { "cik": "884546", "implied_price_usd": 253.79, "manager": "CHARLES SCHWAB INVESTMENT MANAGEMENT INC", "pct_of_13f_total": 1.15, "shares": 102344793, "value_usd": 25974085067 }, { "cik": "1390777", "implied_price_usd": 253.79, "manager": "Bank of New York Mellon Corp", "pct_of_13f_total": 1.03, "shares": 92111869, "value_usd": 23377071068 }, { "cik": "933478", "implied_price_usd": 253.79, "manager": "VANGUARD FIDUCIARY TRUST CO", "pct_of_13f_total": 0.94, "shares": 84194668, "value_usd": 21367764792 }, { "cik": "1871926", "implied_price_usd": 253.79, "manager": "Nuveen, LLC", "pct_of_13f_total": 0.88, "shares": 78503018, "value_usd": 19923281000 }, { "cik": "1330387", "implied_price_usd": 253.79, "manager": "Amundi", "pct_of_13f_total": 0.82, "shares": 73082616, "value_usd": 18547637116 }, { "cik": "914208", "implied_price_usd": 253.79, "manager": "Invesco Ltd.", "pct_of_13f_total": 0.82, "shares": 72890898, "value_usd": 18498980901 } ], "total_shares": 9359437941, "total_value_usd": 2263268012176 }, "market_status": "closed", "symbol": "AAPL" }, "schema_version": "2026-06-17.6" } ``` ## Errors All errors share the envelope `{ "error": { "code", "message", "retryable" } }`. - `400` Invalid symbol or query parameters. - `401` Missing, invalid, or revoked `X-API-Key`. - `403` The key's plan does not include this endpoint. - `404` The symbol is outside the supported universe. Use `/v2/search` to resolve it. - `429` Per-minute rate limit or plan quota exceeded. Honor `Retry-After`. - `503` The data source or key verification is temporarily unavailable. Retryable. ## Code samples ### curl ```bash curl "https://api.stockcontext.com/v2/ownership?symbol=AAPL" \ -H "X-API-Key: $STOCKCONTEXT_API_KEY" ``` ### Python ```python import os import httpx response = httpx.get( "https://api.stockcontext.com/v2/ownership", params={"symbol": "AAPL"}, headers={"X-API-Key": os.environ["STOCKCONTEXT_API_KEY"]}, ) body = response.json() if "error" in body: error = body["error"] raise RuntimeError(f"{error['code']}: {error['message']}") data = body["data"] ``` ### TypeScript ```typescript const res = await fetch("https://api.stockcontext.com/v2/ownership?symbol=AAPL", { headers: { "X-API-Key": process.env.STOCKCONTEXT_API_KEY! }, }); const body = await res.json(); if ("error" in body) { throw new Error(`${body.error.code}: ${body.error.message}`); } const data = body.data; ```