Browse all documentation

Stocks API reference

Prices, history, company events and valuation ratios for listed stocks

Overview

Prices, price history, company events, market cap and valuation ratios for listed stocks. Every request needs a Bearer key, and REST and MCP share one account quota.

A listing id is the exchange's four-letter code, a colon and the ticker, such as XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing, and every answer carries the canonical id. Prices and amounts are decimal strings.

Each listing can carry a company_id. It links the listing to the company in the Filings API, so a price, a valuation and a financial statement meet on one id.

Base URL: https://api.narwhalapi.com. The MCP tool is equities, with one action for each route below.

A company_id joins a listing to the Filings API: financial statements, filings and holders.

Plans and limits

WhatFreeBasicPro
Latest price15 minutes lateLiveLive
Day volume on a priceLeft outIncludedIncluded
Daily bars and past dividends, splits and earningsLast 12 monthsLast 5 yearsFull history
One-minute barsNot includedIncluded, within the plan's historyIncluded, within the plan's history
Search and profileIncludedIncludedIncluded
MoversNot includedIncludedIncluded
Market cap, P/E, P/S, P/B and dividend yieldNot includedIncludedIncluded
Valuation historyNot includedLast 5 yearsFull history
Cost of capital and betaNot includedIncludedIncluded

See pricing for plan prices and monthly request limits. Each request counts as one call unless a route says otherwise.

  • Free prices are delayed by 15 minutes. A delayed answer carries delayed: true and leaves out the day volume.
  • Valuation covers US and Korea listings that match a company in our filings. Korea shows P/E only for now.
  • A ratio that cannot be worked out correctly is left out. It is never sent as 0.
  • The earnings eps_actual is on the market data basis. The EPS behind pe_ratio comes from company filings. The two can differ, so do not compare them directly.
  • Cost of capital covers US and Korea listings that match a company in our filings. You send your own equity risk premium in erp. The method is written once in the cost of capital methodology, and every answer links to it.
  • A bank or an insurer returns 422 not_applicable, because interest is an operating cost for it. An input that cannot be worked out is named in 422 inputs_unavailable, never filled in.
  • Valuation history has one point for each fiscal period end (quarter or year end), not one for each day. Each point uses the close on or just before that day, adjusted for splits only, and the figures as filed for that period.
  • Basic reaches back 5 years and Pro has full history. When your plan cuts older rows, start comes back with the first date served.
  • Earnings are past results only. Upcoming earnings dates are not included yet.
  • Japan profiles carry only name, exchange, country, currency and company_id.

Endpoints

Every route is a GET and needs your key in the Authorization: Bearer header. The examples show the shape of an answer, not live data.

Profile of one stock

GET /v1/equities/{id}

The listing (symbol, name, type, exchange, country, currency), the company's sector, industry and website, and company_id when the listing matches a company in our filings. Open to every plan. Japan listings carry only name, exchange, country, currency and company_id. Fields that are not known are left out.

MCP: tool equities, action profile.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
symbolstringAlwaysTicker on that exchange.
namestringAlwaysName as our symbol list holds it.
typeone of common_stock, preferred_stock, depositary_receipt, reit, closed_end_fund, unit, right, warrant, otherAlwaysWhat kind of instrument it is.
micstringAlwaysThe exchange's four-letter code.
countrystringOptionalCountry of the listing, three letters.
currencystringAlwaysCurrency of the prices.
sectorstringOptionalSector. Left out for Japan listings, which carry no profile text.
industrystringOptionalIndustry. Left out for Japan listings.
websitestringOptionalThe company's website. Left out for Japan listings.
logo_urlstringOptionalOur own copy of the company's logo. Left out when we have none.
company_idstringOptionalThe company's filings id, when the listing matches one cleanly.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "symbol": "MSFT",
  "name": "Microsoft Corporation",
  "type": "common_stock",
  "mic": "XNAS",
  "country": "USA",
  "currency": "USD",
  "sector": "Technology",
  "industry": "Software",
  "website": "https://www.microsoft.com",
  "company_id": "usa:0000789019"
}

Latest price of one stock

GET /v1/equities/{id}/price

Latest price of one listing. Free plans see the price 15 minutes late and the answer carries delayed: true. change_percent is in percent. An unknown id is 404 not_found; a market without prices yet is 404 not_covered.

MCP: tool equities, action price.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
symbolstringAlwaysTicker on that exchange.
currencystringAlwaysCurrency of every price field. Pence are converted to pounds.
pricestringAlwaysLast price, in currency.
previous_closestringOptionalPrevious session's close. Left out when it is not known.
changestringOptionalprice minus previous_close. Left out with previous_close.
change_percentstringOptionalChange since previous_close, in percent (1.5 means 1.5%).
volumestringOptionalShares traded today. Left out when it is not known, and always left out on a delayed answer.
market_sessionone of pre, open, post, closedAlwaysState of the listing's exchange at timestamp: pre, open, post or closed. Only US exchanges have pre and post.
timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
stalebooleanAlwaysTrue when the price is older than 15 minutes while the exchange is open, or could not be refreshed. A closed market is never stale.
delayedalways trueOptionalPresent, as true, only when the price is 15 minutes late (Free plan).
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/price"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "symbol": "MSFT",
  "currency": "USD",
  "price": "421.35",
  "previous_close": "418.90",
  "change": "2.45",
  "change_percent": "0.58",
  "volume": "18234100",
  "market_session": "open",
  "timestamp": "2026-10-26T15:42:00Z",
  "stale": false
}

Latest prices for up to 50 stocks

GET /v1/equities/prices

Latest price of each listed id. One call is counted for each id that returns a price, taken in one step: if fewer calls are left than ids that resolved, the whole request is refused with 429 quota_exceeded. Ids that return no price are listed in errors and cost nothing. Free plans see prices 15 minutes late. change_percent is in percent.

Send up to 50 ids. A batch is charged in one step: if you have fewer calls left than ids that resolve, the whole request returns 429 quota_exceeded.

MCP: tool equities, action prices.

Parameters

ParameterWhereRequiredTypeMeaning
idsqueryYesstringComma-separated listing ids, at most 50 after duplicates are removed. Each id follows the same rules as the single-price route. One call is counted for each id that returns a price.

Response fields

FieldTypeSentMeaning
priceslist of objectAlwaysOne price per id that resolved, by id.
prices[].idstringAlwaysCanonical listing id.
prices[].symbolstringAlwaysTicker on that exchange.
prices[].currencystringAlwaysCurrency of every price field. Pence are converted to pounds.
prices[].pricestringAlwaysLast price, in currency.
prices[].previous_closestringOptionalPrevious session's close. Left out when it is not known.
prices[].changestringOptionalprice minus previous_close. Left out with previous_close.
prices[].change_percentstringOptionalChange since previous_close, in percent (1.5 means 1.5%).
prices[].volumestringOptionalShares traded today. Left out when it is not known, and always left out on a delayed answer.
prices[].market_sessionone of pre, open, post, closedAlwaysState of the listing's exchange at timestamp: pre, open, post or closed. Only US exchanges have pre and post.
prices[].timestampdate-timeAlwaysWhen the price was valid (UTC, whole seconds).
prices[].stalebooleanAlwaysTrue when the price is older than 15 minutes while the exchange is open, or could not be refreshed. A closed market is never stale.
prices[].delayedalways trueOptionalPresent, as true, only when the price is 15 minutes late (Free plan).
errorslist of objectAlwaysIds that returned no price. They are not counted against the quota.
errors[].idstringAlwaysThe id as it was asked for.
errors[].codeone of not_found, not_covered, upstream_busyAlwaysnot_found: no such listing. not_covered: the exchange has no prices yet. upstream_busy: the price could not be fetched now; try again shortly.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/prices?ids=XNAS:MSFT,XNAS:NVDA"
Example response with illustrative values
{
  "prices": [
    {
      "id": "XNAS:MSFT",
      "symbol": "MSFT",
      "currency": "USD",
      "price": "421.35",
      "previous_close": "418.90",
      "change": "2.45",
      "change_percent": "0.58",
      "volume": "18234100",
      "market_session": "open",
      "timestamp": "2026-10-26T15:42:00Z",
      "stale": false
    },
    {
      "id": "XNAS:NVDA",
      "symbol": "NVDA",
      "currency": "USD",
      "price": "188.20",
      "previous_close": "187.10",
      "change": "1.10",
      "change_percent": "0.59",
      "volume": "152003400",
      "market_session": "open",
      "timestamp": "2026-10-26T15:42:00Z",
      "stale": false
    }
  ],
  "errors": [
    {
      "id": "XNAS:NOPE",
      "code": "not_found"
    }
  ]
}

Top US stock movers

GET /v1/equities/movers

The top 25 US stocks and ETFs by direction: gainers, losers or active (most traded by value among the day's biggest gainers and losers, not the whole market). Only ordinary shares and ETFs priced at $1 or more, with at least 100,000 shares and $1 million traded today; ranked by us and cached for 10 minutes. change_percent is in percent. Basic and Pro only (Free gets 403 plan_upgrade_required). market is us for now; another market is 404 not_covered, an unknown direction is 400 invalid_query. One call.

Movers are for the US only. A different market returns 404 not_covered.

MCP: tool equities, action movers.

Parameters

ParameterWhereRequiredTypeMeaning
marketqueryNostringMarket to rank. Only us for now; another market is 404 not_covered.
directionqueryNostringgainers (biggest rise), losers (biggest fall) or active (most traded by value among the day's biggest gainers and losers, not the whole market). Anything else is 400 invalid_query.

Response fields

FieldTypeSentMeaning
marketalways "us"AlwaysThe market ranked.
directionone of gainers, losers, activeAlwaysThe ranking asked for.
moverslist of objectAlwaysUp to 25, best first. Only ordinary shares and ETFs priced at $1 or more, with at least 100,000 shares and $1 million traded today.
movers[].idstringAlwaysCanonical listing id.
movers[].symbolstringAlwaysTicker on that exchange.
movers[].namestringAlwaysCompany or fund name.
movers[].pricestringAlwaysLatest price, in US dollars.
movers[].change_percentstringAlwaysChange since the previous close, in percent (31.4 means 31.4%), 2 places.
movers[].volumestringAlwaysShares traded today.
timestampdate-timeAlwaysWhen the newest price in the list was valid (UTC).
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/movers?market=us&direction=gainers"
Example response with illustrative values
{
  "market": "us",
  "direction": "gainers",
  "movers": [
    {
      "id": "XNAS:NVDA",
      "symbol": "NVDA",
      "name": "NVIDIA Corporation",
      "price": "188.20",
      "change_percent": "4.12",
      "volume": "152003400"
    }
  ],
  "timestamp": "2026-10-26T15:42:00Z"
}

Price bars of one stock

GET /v1/equities/{id}/bars

Daily (1d) or one-minute (1m) bars, oldest first, adjusted for splits and dividends. History: Free 12 months, Basic 5 years, Pro everything; start is sent back when the plan raised it. One-minute bars are for Basic and Pro (Free gets 403 plan_upgrade_required), reach back to 2020 and cover at most 5 days a call (400 range_too_large). Today's daily bar appears after the exchange closes, and for Free 15 minutes after; end is sent back when that cut the range. Up to 5,000 bars a page, with next_cursor; a cursor that no longer matches the prices is 409 cursor_expired. One call a page.

Bars are adjusted for splits and dividends. If a split or dividend changes the history between two pages, the cursor stops working and returns 409 cursor_expired.

MCP: tool equities, action bars.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.
intervalqueryNoone of 1d, 1m1d (one bar per session) or 1m (one-minute bars, Basic and Pro only). Anything else is 400 invalid_interval.
startqueryNostringFirst bar. 1d: a date (YYYY-MM-DD), inclusive; left out means the oldest bar your plan reaches. 1m: a UTC time (YYYY-MM-DDTHH:MM:SSZ, or a date for 00:00 UTC), inclusive; left out means one day before end.
endqueryNostringLast bar. 1d: a date, inclusive; left out means the latest session whose bar is out. 1m: a UTC time, exclusive; left out means now, or five days after start. At most 5 days between start and end for 1m (400 range_too_large).
cursorqueryNostringnext_cursor of the previous page, with the same other parameters. A cursor stops working when the prices behind it changed (a split or dividend): 409 cursor_expired.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
currencystringAlwaysCurrency of every price. Pence become pounds.
adjustedbooleanAlwaysAlways true: prices are adjusted for splits and dividends. A page and the next one always agree; if a split or dividend changes the history between two pages, the cursor stops working (409 cursor_expired) instead of mixing the two.
startstringOptionalSent only when the plan raised the start: the first day (daily) or time (1m) served.
endstringOptionalSent only on Free daily bars, when the 15-minute delay cut the range: the last day served. Today's bar reaches Free 15 minutes after the exchange closes.
barslist of objectAlwaysAt most 5,000 bars a page.
bars[].datestringOptionalSession date (YYYY-MM-DD). Daily bars only; left out on one-minute bars.
bars[].timestampdate-timeAlwaysStart of the bar's period (UTC): the exchange's open for a daily bar, the minute for a one-minute bar.
bars[].openstringAlwaysFirst price of the period.
bars[].highstringAlwaysHighest price of the period.
bars[].lowstringAlwaysLowest price of the period.
bars[].closestringAlwaysLast price of the period.
bars[].volumestringOptionalShares traded in the period. Left out when it is not known.
next_cursorstringAlwaysPass it back to read the next page. Null on the last page.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/bars?interval=1d&start=2026-10-01"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "currency": "USD",
  "adjusted": true,
  "bars": [
    {
      "date": "2026-10-23",
      "timestamp": "2026-10-23T13:30:00Z",
      "open": "417.10",
      "high": "420.55",
      "low": "415.80",
      "close": "418.90",
      "volume": "21034500"
    }
  ],
  "next_cursor": null
}

Dividends of one stock

GET /v1/equities/{id}/dividends

Cash dividends of one listing, newest ex-date first, paged. The list may include announced future rows. The plan sets how far back the past goes: Free 12 months, Basic 5 years, Pro full. start is sent only when older rows were cut off; upcoming rows are never cut. Each request counts one call. An unknown id is 404 not_found; a market without data yet is 404 not_covered.

MCP: tool equities, action dividends.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.
cursorqueryNostringnext_cursor of the previous page, with the same other parameters.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
startdateOptionalFirst day the plan's history window reaches (Free 12 months, Basic 5 years, Pro full). Present only when older rows exist that the plan cannot see; upcoming rows are never cut.
dividendslist of objectAlwaysNewest ex-date first. May include announced future rows.
dividends[].ex_datedateAlwaysFirst day the share trades without the dividend.
dividends[].amountstringAlwaysDividend per share, in currency.
dividends[].currencystringAlwaysCurrency of amount. Pence are converted to pounds.
dividends[].pay_datedateOptionalDay it is paid. Left out when not known.
dividends[].record_datedateOptionalDay the holder must be on record. Left out when not known.
next_cursorstringAlwaysPass as cursor for the next page; null on the last page.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/dividends"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "dividends": [
    {
      "ex_date": "2026-08-20",
      "amount": "0.91",
      "currency": "USD",
      "pay_date": "2026-09-10",
      "record_date": "2026-08-20"
    }
  ],
  "next_cursor": null
}

Splits of one stock

GET /v1/equities/{id}/splits

Share splits of one listing, newest first. The plan sets how far back the past goes: Free 12 months, Basic 5 years, Pro full. start is sent only when older rows were cut off; upcoming rows are never cut. Each request counts one call. An unknown id is 404 not_found; a market without data yet is 404 not_covered.

MCP: tool equities, action splits.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
startdateOptionalFirst day the plan's history window reaches (Free 12 months, Basic 5 years, Pro full). Present only when older rows exist that the plan cannot see; upcoming rows are never cut.
splitslist of objectAlwaysNewest first.
splits[].datedateAlwaysFirst day the shares trade split-adjusted.
splits[].ratiostringAlwaysNew shares for old: 10:1 gives ten shares for each one held; 1:10 is a reverse split.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:NFLX/splits"
Example response with illustrative values
{
  "id": "XNAS:NFLX",
  "splits": [
    {
      "date": "2025-11-17",
      "ratio": "10:1"
    }
  ]
}

Earnings of one stock

GET /v1/equities/{id}/earnings

Past earnings reports of one listing (dated today or earlier at the exchange), with estimate, actual and surprise, newest first, paged. Upcoming report dates are not included yet. surprise_percent is in percent. eps_actual is on the market data basis and can differ from the filings EPS in the valuation. filing_id names the annual or quarterly report that covers a past report when we hold it. The plan sets how far back the past goes: Free 12 months, Basic 5 years, Pro full. start is sent only when older rows were cut off. Each request counts one call. An unknown id is 404 not_found; a market without data yet is 404 not_covered.

This route returns past reports only. Upcoming report dates are not included yet.

filing_id links a past report to the filing that covers it. Pass it to GET /v1/filings/{filing_id} in the Filings API.

MCP: tool equities, action earnings.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.
cursorqueryNostringnext_cursor of the previous page, with the same other parameters.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
startdateOptionalFirst day the plan's history window reaches (Free 12 months, Basic 5 years, Pro full). Present only when older rows exist that the plan cannot see.
earningslist of objectAlwaysPast reports only, newest first. Upcoming report dates are not included yet.
earnings[].datedateAlwaysReport day (today or earlier at the exchange).
earnings[].timeone of before_open, after_close, duringOptionalWhen in the day it reports. Left out when not known.
earnings[].eps_estimatestringOptionalAnalysts' earnings per share estimate. Left out when there is none.
earnings[].eps_actualstringOptionalReported earnings per share on the market data basis, which can differ from the filings EPS in eps_ttm of the valuation. Left out until it is reported.
earnings[].surprisestringOptionaleps_actual minus eps_estimate, rounded to 2 places.
earnings[].surprise_percentstringOptionalsurprise as a percent of the estimate (8.31 means 8.31%), 2 places.
earnings[].filing_idstringOptionalThe annual, semiannual or quarterly report in our filings that covers this report: pass it to the filings GET /v1/filings/{filing_id}. Left out when the company has no filings match and when no report fits the date.
next_cursorstringAlwaysPass as cursor for the next page; null on the last page.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/earnings"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "earnings": [
    {
      "date": "2026-07-29",
      "time": "after_close",
      "eps_estimate": "3.31",
      "eps_actual": "3.45",
      "surprise": "0.14",
      "surprise_percent": "4.23",
      "filing_id": "usa:0000789019:0000950170-26-000123"
    }
  ],
  "next_cursor": null
}

Market cap, P/E, P/S, P/B and dividend yield of one stock

GET /v1/equities/{id}/valuation

Market cap (latest price times shares outstanding), P/E (latest price over trailing 12-month diluted EPS from company filings, GAAP or IFRS as reported), P/S (market cap over trailing 12-month revenue), P/B (market cap over total equity of the latest balance sheet) and dividend yield (trailing 12-month dividends per share over the price, in percent) of one US or Korean listing, with the inputs and the dates they are as of. Korea has P/E only for now; market cap, P/S, P/B and yield come with share counts and dividends. A figure that cannot be worked out correctly is left out, never sent as 0: market cap after a split since the share count, for several share classes without a count each, and for a depositary receipt or foreign filer; P/E when EPS is negative or under 0.01, reported in another currency, or for a depositary receipt. Another class of a company, such as a preferred line, has no figures of its own and shows none. Basic and Pro only (Free gets 403 plan_upgrade_required). A listing outside the US and Korea, or without a clean company match, is 404 not_covered. One call; paid plans see live prices.

Each ratio comes with its inputs and the dates they are as of: shares_as_of, eps_period_end, revenue_period_end, equity_as_of and dividends_period_end. Paid plans see live prices.

MCP: tool equities, action valuation.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
currencystringAlwaysCurrency of market_cap and of the price behind pe_ratio.
market_capstringOptionalLatest price times shares_outstanding, in currency, whole units. Left out when a split happened after shares_as_of, when the company has several share classes without a count for each, and for a depositary receipt or foreign filer. Korea has no market cap yet: its listings show P/E only for now.
pe_ratiostringOptionalLatest price divided by eps_ttm, 2 places. Left out when eps_ttm is negative or under 0.01, when the reporting currency is not currency, when a split happened after eps_period_end, and for a depositary receipt.
ps_ratiostringOptionalPrice to sales: market_cap divided by revenue_ttm, 2 places. Left out whenever market_cap is, when revenue is not above 0, and when it is reported in another currency than currency.
pb_ratiostringOptionalPrice to book: market_cap divided by total_equity, 2 places. Left out whenever market_cap is, when total equity is 0 or negative, and when it is reported in another currency than currency.
dividend_yieldstringOptionalDividends per share of the last 12 months over the latest price, in percent (0.71 means 0.71%), 2 places. Left out when no dividend is on file for those 12 months (a company that pays none included), when a split happened after dividends_period_end, when the dividends are in another currency than currency, and for a depositary receipt.
shares_outstandingstringOptionalShares outstanding of the company's main common share class. Left out for a listing that is another class, such as a preferred line, which has no count here.
shares_as_ofdateOptionalDate shares_outstanding is counted at. Shown whenever the count is.
eps_ttmstringOptionalTrailing 12-month diluted EPS from company filings (GAAP or IFRS as reported); the fourth quarter is the annual figure minus the first three. Shown whenever it can be worked out; left out for a listing that is another class of the company. It can differ from the eps_actual of stock earnings, which is on the market data basis.
eps_period_enddateOptionalEnd of the newest quarter in eps_ttm. Shown whenever eps_ttm is.
revenue_ttmstringOptionalRevenue of the last 12 months from company filings, whole units of the reporting currency (the fourth quarter is the annual figure minus the first three). Left out when no four quarters or full year are on file and for a listing that is another class of the company.
revenue_period_enddateOptionalEnd of the newest quarter in revenue_ttm. Shown whenever it is.
total_equitystringOptionalTotal equity of the latest balance sheet, including non-controlling interests, whole units of the reporting currency. Can be negative.
equity_as_ofdateOptionalBalance sheet date of total_equity. Shown whenever it is.
dividend_per_share_ttmstringOptionalDividends declared per share of the main common class over the last 12 months, in the reporting currency. Shown whenever it can be worked out.
dividends_period_enddateOptionalEnd of the newest quarter in dividend_per_share_ttm. Shown whenever it is.
company_idstringAlwaysThe company in our filings that the listing matches.
timestampdate-timeAlwaysWhen the price behind the answer was valid (UTC).
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/valuation"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "currency": "USD",
  "market_cap": "3130000000000",
  "pe_ratio": "33.50",
  "ps_ratio": "11.40",
  "pb_ratio": "10.20",
  "dividend_yield": "0.86",
  "shares_outstanding": "7430000000",
  "shares_as_of": "2026-07-24",
  "eps_ttm": "12.58",
  "eps_period_end": "2026-06-30",
  "revenue_ttm": "274500000000",
  "revenue_period_end": "2026-06-30",
  "total_equity": "307000000000",
  "equity_as_of": "2026-06-30",
  "dividend_per_share_ttm": "3.64",
  "dividends_period_end": "2026-06-30",
  "company_id": "usa:0000789019",
  "timestamp": "2026-10-26T15:42:00Z"
}

Beta, cost of equity, cost of debt and WACC of one stock

GET /v1/equities/{id}/cost-of-capital

Beta of one US or Korean stock against a benchmark ETF (weekly returns over two years by default, from prices adjusted for splits and dividends), the risk-free rate (the 10-year point of the country's government yield curve, or yours), the cost of equity (risk-free plus beta times the equity risk premium: yours in erp, or by default our own published total premium of the stock's country), the cost of debt (interest expense over average total debt from company filings, or, where that cannot be used, the risk-free rate plus a public table's default spread for the company's interest coverage; before and after tax, with the country's corporate tax rate from a public table unless you send tax_rate) and WACC, weighted by market cap and total debt. Every input is in the answer with its value, its basis and its date. Banks and insurers are 422 not_applicable; an input that cannot be worked out correctly is named in 422 inputs_unavailable, never repaired; a number that is shown but needs care comes with a warnings entry. Basic and Pro only (Free gets 403 plan_upgrade_required). One call.

erp is required. Korea has no yield curve here, so send risk_free for a Korean stock. Every input comes with its value and date, and warnings lists numbers to check before you rely on them.

MCP: tool equities, action cost_of_capital.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.
erpqueryNostringYour equity risk premium, in percent (5 means 5%). Left out, it is our own published total equity risk premium of the stock's country (the answer names its date and basis). Where we publish none, without it the answer is 400 erp_required. Above 0 and at most 25.
benchmarkqueryNostringListing id of the market proxy beta is measured against: an ETF, fund or stock priced in the same currency as the stock. Left out, it is ARCX:SPY for a US stock, XKRX:069500 for a Korean one and XTKS:1306 for a Japanese one; any other market needs it.
windowqueryNoone of 2y, 5yHow far back the returns reach: 2y (default) or 5y.
frequencyqueryNoone of weekly, monthlyReturn interval: weekly (default; the last common trading day of each ISO week) or monthly.
betaqueryNoone of raw, adjustedWhich beta feeds the cost of equity: raw (default) or adjusted (0.67 raw plus 0.33). Both are always in the answer.
risk_freequeryNostringYour own risk-free rate, in percent. Left out, it is the 10-year point of the country's government yield curve. Needed where we have no curve (Korea): without it the answer is 422 risk_free_unavailable.
tax_ratequeryNostringYour own tax rate for the debt shield, in percent. Left out, it is the corporate tax rate of the stock's country from Damodaran's public table (PwC rates). Between 0 and 60.
periodqueryNoone of ttm, annualPeriod of interest expense: the last 12 months (ttm, default) or the latest full fiscal year (annual). Korea and Japan have annual figures only, so annual is what they get.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
currencystringAlwaysCurrency of the money fields.
company_idstringAlwaysThe company in our filings that the listing matches.
betaobjectAlways
beta.benchmarkobjectAlways
beta.benchmark.idstringAlwaysListing id of the benchmark.
beta.benchmark.defaultalways trueOptionalPresent, as true, when the benchmark is our default for the stock's market.
beta.windowone of 2y, 5yAlways
beta.frequencyone of weekly, monthlyAlways
beta.startdateAlwaysDate of the first price behind the first return.
beta.enddateAlwaysDate of the last price behind the last return.
beta.observationsintegerAlwaysNumber of returns in the regression.
beta.rawstringAlwaysSlope of the stock's returns on the benchmark's, both from prices adjusted for splits and dividends.
beta.adjustedstringAlways0.67 times raw plus 0.33.
beta.usedone of raw, adjustedAlwaysThe beta in cost_of_equity.
beta.r_squaredstringAlwaysShare of the stock's variance explained.
beta.std_errorstringAlwaysStandard error of raw.
risk_freeobjectAlways
risk_free.ratestringAlwaysThe risk-free rate, in percent (4.25 means 4.25%).
risk_free.sourceone of yield_curve, callerAlways
risk_free.basisone of ours, callerAlwaysours: the official government yield curve we publish. caller: yours.
risk_free.countrystringOptionalCountry of the yield curve. Not sent for caller.
risk_free.maturityalways "10y"OptionalPoint of the curve. Not sent for caller.
risk_free.curve_typestringOptionalHow the curve is quoted (par, zero-coupon, Svensson, yield to maturity): they differ a little between countries. Not sent for caller.
risk_free.as_ofdateOptionalDate of the curve. Not sent for caller.
equity_risk_premiumobjectAlways
equity_risk_premium.ratestringAlwaysThe equity risk premium, in percent (4.25 means 4.25%).
equity_risk_premium.sourceone of narwhal, callerAlwaysWho set it: narwhal, our own published total equity risk premium of the stock's country, or caller, the erp you sent.
equity_risk_premium.countrystringOptionalCountry of our premium, ISO 3166-1 alpha-3. narwhal only.
equity_risk_premium.as_ofdateOptionalDate our premium describes. narwhal only.
equity_risk_premium.basisone of observed, modelled, tableOptionalHow our total premium was built: observed from our own data, modelled by our spread model, table with the country part taken from a public table. narwhal only.
cost_of_equitystringAlwaysRisk-free rate plus beta times equity risk premium, in percent (4.25 means 4.25%).
cost_of_debtobjectOptionalLeft out for a company with no debt on file.
cost_of_debt.pre_taxstringAlwaysInterest expense over average total debt, in percent (4.25 means 4.25%). Where that cannot be used (interest not positive, or a result under 0.1% or over 30%) and EBIT is on file: the risk-free rate plus the default spread of the company's interest coverage ratio from a public table; basis says which.
cost_of_debt.basisone of ours, tableAlwaysours: interest expense over average debt from the company's filings. table: risk-free rate plus the table's default spread.
cost_of_debt.interest_coveragestringOptionalEBIT over interest expense. table only; left out when there is no interest.
cost_of_debt.default_spreadstringOptionalDefault spread of that coverage ratio, in percent (4.25 means 4.25%). table only.
cost_of_debt.tablestringOptionalName of the spread table. table only.
cost_of_debt.table_updatestringOptionalThe update of the table, for example January 2026. table only.
cost_of_debt.table_urlstringOptionalWhere the table is published. table only.
cost_of_debt.after_taxstringOptionalpre_tax times one minus the tax rate. Left out while no tax rate is known.
cost_of_debt.periodone of ttm, annualAlways
cost_of_debt.period_enddateAlwaysEnd of the newest quarter or year in the interest.
cost_of_debt.interest_expensestringAlwaysInterest expense of the period, whole units of currency.
cost_of_debt.average_debtstringAlwaysTotal debt at the start and at the end of the period, averaged; the end balance alone when the start is not on file. Whole units of currency; leases excluded.
cost_of_debt.opening_debtstringOptionalTotal debt a year before period_end. Left out when it is not on file.
cost_of_debt.closing_debtstringAlwaysTotal debt at period_end.
tax_rateobjectOptionalLeft out when the country has no rate in the table. Send tax_rate to set one.
tax_rate.ratestringAlwaysThe tax rate on debt, in percent (4.25 means 4.25%).
tax_rate.basisone of table, callerAlwaystable: the country's rate from a public table. caller: yours.
tax_rate.sourceone of country_table, callerAlwayscountry_table: the corporate tax rate of the stock's country from a public table. caller: the tax_rate you sent.
tax_rate.countrystringOptionalISO 3166-1 alpha-3 code of the country the rate is for. Table rate only.
tax_rate.tablestringOptionalName of the table, with its publisher and source. Table rate only.
tax_rate.table_updatestringOptionalThe update of the table the rate is from, for example January 2026.
tax_rate.table_urlstringOptionalWhere the table is published. Table rate only.
weightsobjectAlways
weights.equitystringAlwaysMarket cap over market cap plus debt, in percent (4.25 means 4.25%).
weights.debtstringAlwaysDebt over market cap plus debt, in percent (4.25 means 4.25%).
weights.market_capstringAlwaysLatest price times shares outstanding, whole units of currency.
weights.market_cap_as_ofdate-timeAlwaysWhen the price behind market_cap was valid.
weights.total_debtstringAlwaysLatest total debt on file, whole units of currency; leases excluded.
weights.total_debt_as_ofdateAlwaysBalance sheet date of total_debt.
waccstringOptionalWeighted average cost of capital, in percent (4.25 means 4.25%). Equals cost_of_equity for a company with no debt. Left out while debt exists and no tax rate is known.
warningslist of objectAlwaysThings to check before using the numbers. Empty when there are none.
warnings[].codestringAlwaysStable machine-readable code.
warnings[].messagestringAlwaysWhat it means, in plain words.
methodology_urlstringAlwaysHow every figure is worked out.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/cost-of-capital?erp=5"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "currency": "USD",
  "company_id": "usa:0000789019",
  "beta": {
    "benchmark": {
      "id": "ARCX:SPY",
      "default": true
    },
    "window": "2y",
    "frequency": "weekly",
    "start": "2024-10-25",
    "end": "2026-10-16",
    "observations": 103,
    "raw": "1.0842",
    "adjusted": "1.0564",
    "used": "raw",
    "r_squared": "0.5731",
    "std_error": "0.0894"
  },
  "risk_free": {
    "rate": "4.1200",
    "source": "yield_curve",
    "basis": "ours",
    "country": "USA",
    "maturity": "10y",
    "curve_type": "nominal_par",
    "as_of": "2026-10-22"
  },
  "equity_risk_premium": {
    "rate": "5.0000",
    "source": "caller"
  },
  "cost_of_equity": "9.5410",
  "cost_of_debt": {
    "pre_tax": "3.9100",
    "basis": "ours",
    "after_tax": "2.9078",
    "period": "ttm",
    "period_end": "2026-06-30",
    "interest_expense": "2900000000",
    "average_debt": "74200000000",
    "opening_debt": "72000000000",
    "closing_debt": "76400000000"
  },
  "tax_rate": {
    "rate": "25.6300",
    "source": "country_table",
    "basis": "table",
    "country": "USA",
    "table": "Damodaran, corporate marginal tax rates by country (PwC, January 2026 update)",
    "table_update": "January 2026",
    "table_url": "https://pages.stern.nyu.edu/~adamodar/New_Home_Page/datafile/countrytaxrates.html"
  },
  "weights": {
    "equity": "98.0000",
    "debt": "2.0000",
    "market_cap": "3893412000000",
    "market_cap_as_of": "2026-10-26T15:42:00Z",
    "total_debt": "76400000000",
    "total_debt_as_of": "2026-06-30"
  },
  "wacc": "9.4083",
  "warnings": [],
  "methodology_url": "https://narwhalapi.com/methodology/cost-of-capital"
}

Valuation history of one stock

GET /v1/equities/{id}/valuation/history

Market cap, P/E, P/S, P/B and dividend yield at every fiscal period end (quarter or year end) of one US or Korean listing, oldest first. Each point is the closing price on or just before that day, adjusted for splits only, and the figures filed for that period (trailing 12-month EPS, revenue and dividends, total equity, the nearest share count), so a later restatement is in it. A figure that cannot be worked out is left out, never sent as 0, by the same rules as the valuation. History: Basic 5 years, Pro everything; start is sent back when the plan cut older periods. Basic and Pro only (Free gets 403 plan_upgrade_required). A listing outside the US and Korea, or without a clean company match, is 404 not_covered. One call.

MCP: tool equities, action valuation_history.

Parameters

ParameterWhereRequiredTypeMeaning
idpathYesstringListing id: the exchange's four-letter code, a colon and the ticker, for example XNAS:MSFT or XNYS:BRK.B. A bare US ticker such as MSFT also works when it names one US listing; the answer carries the canonical id. Share classes use a dot (BRK.B); BRK-B is accepted.

Response fields

FieldTypeSentMeaning
idstringAlwaysCanonical listing id.
currencystringAlwaysCurrency of every price and market cap.
company_idstringAlwaysThe company in our filings that the listing matches.
startdateOptionalFirst day the plan's history window reaches (Basic 5 years, Pro full). Present only when older periods exist that the plan cannot see.
pointslist of objectAlwaysOne point per fiscal period end of the company, oldest first. Each is worked out from the figures filed for that period and the price on that day, not from what was known then: a later restatement is in it. A period without a price, or without any figure, has no point.
points[].datedateAlwaysA fiscal period end of the company (quarter or year end): every figure in the point is as of this day.
points[].pricestringOptionalClosing price on price_date, in currency, adjusted for splits only (never for dividends), so it is on today's share basis. Left out, with every figure below, when the prices of that period could not be read right now: try again later.
points[].price_datedateOptionalLast trading day on or before date (at most 7 days earlier) the price is from.
points[].market_capstringOptionalprice times the share count nearest to date (counted from 130 days before to 45 days after it), on today's share basis, whole units of currency. Left out when there is no such count, for several share classes without a count for each, and for a depositary receipt or foreign filer.
points[].pe_ratiostringOptionalprice over diluted EPS of the four quarters ending on date, 2 places. Left out when EPS is negative or under 0.01, not reported in currency, or the four quarters (or the year) ending on date are not on file, and for a depositary receipt.
points[].ps_ratiostringOptionalmarket_cap over revenue of the four quarters ending on date, 2 places. Left out whenever market_cap is, or the revenue is not on file, not above 0 or not in currency.
points[].pb_ratiostringOptionalmarket_cap over total equity of the balance sheet dated date, 2 places. Left out whenever market_cap is, or the equity is not on file, not above 0 or not in currency.
points[].dividend_yieldstringOptionalDividends per share of the four quarters ending on date over price, in percent (0.71 means 0.71%), 2 places. Left out when no dividend is on file for those quarters, they are not in currency, and for a depositary receipt.
Request
curl -H "Authorization: Bearer $NARWHAL_API_KEY" \
  "https://api.narwhalapi.com/v1/equities/XNAS:MSFT/valuation/history"
Example response with illustrative values
{
  "id": "XNAS:MSFT",
  "currency": "USD",
  "company_id": "usa:0000789019",
  "points": [
    {
      "date": "2026-06-30",
      "price": "497.40",
      "price_date": "2026-06-30",
      "market_cap": "3697000000000",
      "pe_ratio": "39.54",
      "ps_ratio": "13.47",
      "pb_ratio": "12.04",
      "dividend_yield": "0.73"
    }
  ]
}

Errors

Errors use the shared problem shape with a request_id. See Errors and quota for the shape and the quota headers. Failed requests are not charged.

CodeStatusMeaning
invalid_query400A parameter is missing, unknown, repeated or outside its range.
invalid_id400An id or pair is not written the way this API expects.
too_many_ids400A batch holds more than 50 ids.
invalid_interval400The interval is not 1d or 1m, or not available for this instrument.
range_too_large400The window is longer than the route allows.
plan_upgrade_required403The request needs a Basic or Pro plan.
not_found404No such listing, fund or pair.
not_covered404The market, country or company is not covered yet.
invalid_cursor400The cursor is not one this request returned, or it comes from a different request.
cursor_expired409The prices behind the cursor changed. Start again without the cursor.
quota_exceeded429Your plan has fewer calls left than the request needs.
upstream_busy503A price could not be fetched now. In a batch it is listed in errors. Try again shortly.
area_unavailable503This area cannot answer right now.
erp_required400Send erp, your equity risk premium in percent.
insufficient_history422The stock and its benchmark share fewer returns than 80% of the window.
risk_free_unavailable422No yield curve for this country. Send risk_free.
not_applicable422WACC is not meaningful for a bank or an insurer.
currency_mismatch422The benchmark or the filings are in another currency than the price.
inputs_unavailable422An input cannot be worked out correctly. The detail names it.