Skip to main content
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.

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.
Two design choices follow from those contracts and surprise people often enough to call out here.

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

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)
{
  "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

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.