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

Failure is exactly a top-level error object with three fields. This is a live 429 from /v2/snapshot:
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 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

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.
FieldMeaning
symbolThe resolved symbol for endpoints that take one (normalized, e.g. BRK-B).
as_ofThe timestamp the payload represents: upstream timestamp when the source gives one, otherwise generation time. UTC ISO-8601. See freshness.
freshnessOne of intraday, end_of_day, stale, degraded, unsupported. Owned by freshness.
market_statusopen or closed. Omitted on stateless lookup routes (/v2/search, /v2/coverage). Owned by freshness.
cache_age_secondsSeconds 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.
currencyPresent only when the payload carries monetary values; USD across the supported universe.
asset_typestock or etf. Present where the two shapes branch.
periodPresent on period-based endpoints (/v2/fundamentals, /v2/history). Units owned by 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

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:
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 lists which families return unsupported for which assets.

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.
PatternWhat it looks likeMeaningWhat to do
null"field": nullThe 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 keythe key is simply not presentA 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:
{
  "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

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.
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"]
The caller catches NoxstockError and checks retryable before deciding to retry. The full backoff loop that honors Retry-After lives with the error codes.

Error codes

Every code, its HTTP status, whether it is retryable, and what to do about it.