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.
Response model
Success is exactly
{ "data": ... }; failure is exactly { "error": { "code", "message", "retryable" } }. Never parse a raw object at the top level.Freshness
freshness, as_of, and market_status tell you how current a response is before you show or cache it.Units and fields
_usd is whole U.S. dollars, _pct is a percentage rather than a decimal, and signs are meaningful.Coverage and gaps
5,628 US-listed symbols, plus which data families do not apply to foreign filers and ETFs.
Unsupported is not an error
When a route does not apply to a symbol or asset type, you get HTTP 200 with a normaldata 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
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:GET /v2/snapshot?symbol=SPY (one field, trimmed)
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
MCP tools are read-only REST passthroughs. A successful tool call returns the RESTdata 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.