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

These are the suffixes you will see across the API, each verified against live fixtures.
SuffixMeaningExample from a live response
_usdWhole US dollars, not thousands, not millions.market_cap_usd: 4512101567600 is 4.512T;valueusd:15551000.0is4.512T; `value_usd: 15551000.0` is 15.55M.
_pctPercentage points, already scaled, not a 0–1 fraction.dividend_yield_pct: 0.35 means 0.35%; fcf_margin_pct: 24.04 means 24.04%.
_secondsA duration in seconds.cache_age_seconds: 16096.
share-count fieldsWhole share counts.shares_outstanding: 14725873000, plus shares_basic, shares_diluted, shares_outstanding_cover_page.
no suffixA 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

_pct values are percentage points. To use one as a multiplier, divide by 100 first.
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

A negative number is information. Preserve the sign through your pipeline.
{
  "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

Point-in-time timestamps are UTC ISO-8601 with a Z:
"as_of": "2026-06-05T14:19:06Z"
Date-only fields use YYYY-MM-DD:
"period_end": "2026-03-28"
Keep the two kinds separate. as_of timestamps the payload and its source context (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

/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):
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

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

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: check there before writing absence-handling code, and do not invent a field that the response did not return.