A Rust client for the Yahoo! Finance API: historical market data, ticker fundamentals, and financial events.
- Historical quotes (OHLCV) with configurable intervals and ranges, including pre/post market data
- Corporate actions: dividends, splits, capital gains
- Ticker fundamentals: 20
quoteSummarymodules (analyst estimates and recommendations, earnings calendar, holders and insider activity, SEC filings, fund profiles and top holdings) - Financial events: earnings, meetings, calls
- Ticker search
Async by default; an optional blocking feature provides a synchronous API.
| method | purpose |
|---|---|
get_latest_quotes(ticker, interval) |
latest quotes |
get_quote_history(ticker, start, end) |
quotes for a date range (daily interval) |
get_quote_range(ticker, interval, range) |
quotes for a range label (1mo, 1y, ...) |
get_quote_history_interval(ticker, start, end, interval) |
quotes for a date range with a custom interval |
get_quote_history_interval_prepost(ticker, start, end, interval, prepost) |
same, with pre/post market data |
get_quote_period_interval(ticker, range, interval, prepost) |
quotes for a range label with interval and pre/post market data |
search_ticker(name) / search_ticker_opt(name) |
search for tickers (optional fields kept or replaced by defaults) |
get_ticker_info(symbol) |
ticker fundamentals (quoteSummary modules) |
get_financial_events(ticker, limit) |
earnings, meeting and call dates (max 250) |
get_earnings_only(ticker, limit) |
earnings events only |
The YResponse returned by the quote methods provides quotes(), last_quote(), metadata(), dividends(), splits() and capital_gains().
With the blocking feature enabled, all of the above methods are available on the blocking connector as well.
[dependencies]
yahoo_finance_api = "5"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
time = { version = "0.3", features = ["macros"] }
reqwest = "0.13"tokio is only needed to run the async examples below. time is used by
the date-based examples (datetime! macro, OffsetDateTime); as an
alternative the crate re-exports the time crate as yahoo_finance_api::time
so no direct dependency is strictly required — but then the imports in the
examples below need to be rewritten from use time::... to
use yahoo_finance_api::time::.... reqwest is only needed for the
custom-client and proxy examples.
Minimum supported Rust version: 1.70. See ReleaseNotes.md for the changelog.
| feature | description |
|---|---|
blocking |
blocking (non-async) API |
governor |
proactive rate limiting, 10 requests/sec by default |
decimal |
represent prices as rust_decimal::Decimal instead of f64 |
debug |
include the full response body (truncated) in deserialization error messages |
The examples below use #[tokio::main]; in a real application, any async runtime works. With the blocking feature, the same methods are available without async/await.
Get the latest quote. Note that get_latest_quotes actually returns the last
month of quotes (it internally uses the 1mo range); use
response.last_quote() to extract the most recent one:
use yahoo_finance_api as yahoo;
use time::OffsetDateTime;
use tokio;
#[tokio::main]
async fn main() {
let provider = yahoo::YahooConnector::new().unwrap();
let response = provider.get_latest_quotes("AAPL", "1d").await.unwrap();
let quote = response.last_quote().unwrap();
let time = OffsetDateTime::from_unix_timestamp(quote.timestamp).unwrap();
println!("At {} the price of Apple was {}", time, quote.close);
}Get the quote history for a date range, or for a range label and interval:
use yahoo_finance_api as yahoo;
use time::macros::datetime;
use tokio;
#[tokio::main]
async fn main() {
let provider = yahoo::YahooConnector::new().unwrap();
// By start/end dates (daily interval)
let start = datetime!(2024-1-1 0:00:00.00 UTC);
let end = datetime!(2024-1-31 23:59:59.99 UTC);
let resp = provider.get_quote_history("AAPL", start, end).await.unwrap();
let january_quotes = resp.quotes().unwrap();
// By range label + interval
let resp = provider.get_quote_range("AAPL", "1d", "1mo").await.unwrap();
let last_month = resp.quotes().unwrap();
// Custom interval, e.g. weekly
let resp = provider.get_quote_history_interval("AAPL", start, end, "1wk").await.unwrap();
// Intraday with pre/post market data
let resp = provider.get_quote_period_interval("AAPL", "5d", "5m", true).await.unwrap();
}YResponse also exposes the dividends, splits and capital gains recorded in the requested period
(only for responses obtained via get_quote_history*/get_quote_range/get_latest_quotes —
get_quote_period_interval requests the events data too, but Yahoo typically returns none
for intraday ranges, so these lists are usually empty there).
Note that Yahoo currently often omits the capitalGains event entirely, in which case
capital_gains() returns an empty list:
use yahoo_finance_api as yahoo;
use tokio;
#[tokio::main]
async fn main() {
let provider = yahoo::YahooConnector::new().unwrap();
let resp = provider.get_quote_range("AAPL", "1d", "1y").await.unwrap();
let dividends = resp.dividends().unwrap();
let splits = resp.splits().unwrap();
}Ready-to-run examples are in examples/.
get_ticker_info fetches detailed fundamental data about a ticker in a single request. It always
requests all 20 modules, and the returned YQuoteSummary contains a single YSummaryData holding
every module as an Option field; a module is None if Yahoo did not return it for that ticker
(e.g. fundProfile/topHoldings exist only for funds and ETFs, and futures/currencies/indexes
only get a small subset):
use yahoo_finance_api as yahoo;
use tokio;
#[tokio::main]
async fn main() {
let mut provider = yahoo::YahooConnector::new().unwrap();
let result = provider.get_ticker_info("AAPL").await.unwrap();
// The modules that apply to the asset type; missing modules are None
let Some(summary) = result.quote_summary.and_then(|q| q.result).and_then(|mut r| r.pop()) else {
println!("no quote summary in response");
return;
};
// Company profile, key statistics, financial data, ...
if let Some(profile) = summary.asset_profile {
println!("City: {:?}", profile.city);
}
// Analyst recommendations and estimates
if let Some(recs) = summary.recommendation_trend {
println!("Recommendations: {:?}", recs.trend);
}
// Upcoming earnings / dividend dates
if let Some(calendar) = summary.calendar_events {
println!("Calendar: {:?}", calendar);
}
// Top institutional holders
if let Some(holders) = summary.institution_ownership {
if let Some(top) = holders.ownership_list.first() {
println!("Top holder: {:?}", top.organization);
}
}
}Available modules (all fields are Option):
| module | description |
|---|---|
assetProfile, summaryDetail, defaultKeyStatistics, quoteType, financialData |
company profile, valuation and key statistics |
recommendationTrend |
analyst recommendation counts (strongBuy/buy/hold/sell/strongSell) |
earningsTrend, earningsHistory, earnings |
analyst estimates, past EPS surprises, earnings charts |
upgradeDowngradeHistory |
analyst rating changes |
calendarEvents |
upcoming earnings, dividend and ex-dividend dates |
insiderHolders, insiderTransactions, majorHoldersBreakdown, institutionOwnership, fundOwnership, netSharePurchaseActivity |
holders and insider activity |
fundProfile, topHoldings |
fund metadata and top holdings (funds/ETFs only) |
secFilings |
SEC filings list |
Note: for futures, currencies and indexes, Yahoo only returns a small subset of these modules.
Retrieve earnings, meeting and call dates for a ticker (up to 250 events):
use yahoo_finance_api as yahoo;
use tokio;
#[tokio::main]
async fn main() {
let mut provider = yahoo::YahooConnector::new().unwrap();
let events = provider.get_financial_events("AAPL", 100).await.unwrap();
for event in events {
println!("{}: estimate {:?}, actual {:?}", event.earnings_date, event.eps_estimate, event.reported_eps);
}
// Only earnings events (meetings and calls filtered out):
let earnings = provider.get_earnings_only("AAPL", 100).await.unwrap();
}
⚠️ Warning:get_financial_events/get_earnings_onlyquery Yahoo'sv1/finance/visualizationendpoint, which Yahoo is no longer updating (as of 2025). Live checks show it returns historical earnings dates but stops at the last quarter Yahoo processed — e.g. AAPL returned nothing newer than 2025-05-01 when last verified. Treat the returned list as historical data, not as a reliable source for upcoming earnings dates. (yfinance migrated to HTML-scrapingfinance.yahoo.com/calendar/earningsfor the same reason.)
use yahoo_finance_api as yahoo;
use tokio;
#[tokio::main]
async fn main() {
let provider = yahoo::YahooConnector::new().unwrap();
let resp = provider.search_ticker("Apple").await.unwrap();
for item in resp.quotes {
println!("{}", item.symbol);
}
}Some fields like longname are only optional and will be replaced by default values if missing (e.g. empty string). If you do not like this behavior, use search_ticker_opt instead, which keeps Option<String> fields and returns None when a field is missing.
To prevent overwhelming the Yahoo! Finance API and avoid getting rate-limited (HTTP 429), enable the optional governor feature. This integrates a proactive rate limiter: the library waits before sending a request instead of returning a rate limit error.
[dependencies]
yahoo_finance_api = { version = "5", features = ["governor"] }When enabled, YahooConnector defaults to 10 requests per second. Override or disable it at runtime via the builder (this API is only available when the governor feature is active):
use yahoo_finance_api as yahoo;
use std::num::NonZeroU32;
fn main() {
// 1. Default (10 requests/sec when `governor` feature is active)
let provider = yahoo::YahooConnector::new().unwrap();
// 2. Custom limit (e.g. 5 requests/sec)
let provider = yahoo::YahooConnector::builder()
.rate_limit(Some(NonZeroU32::new(5).unwrap()))
.build().unwrap();
// 3. Explicitly disable rate limiter even when `governor` feature is compiled in
let provider = yahoo::YahooConnector::builder()
.rate_limit(None)
.build().unwrap();
}The library retries transient failures automatically:
- Chart requests (
get_quote_*) retry once when Yahoo answers with an empty body, an HTML block page, or a 5xx. get_ticker_infoandget_financial_eventsretry once after refreshing the crumb+cookie pair on 401/403/5xx, on a parse error, and on 200-JSONInvalid Crumb/Unauthorizedresponses.
HTTP 429 and plain-text too many requests bodies are definitive and never retried.
YahooConnector is built via YahooConnectorBuilder; start with
YahooConnector::builder() (equivalent to YahooConnectorBuilder::new())
and configure the underlying HTTP client before calling build():
use yahoo_finance_api as yahoo;
use std::time::Duration;
fn main() {
// Request timeout and custom user agent
let provider = yahoo::YahooConnector::builder()
.timeout(Duration::from_secs(30))
.user_agent("my-app/1.0")
.build()
.unwrap();
// Route all requests through a proxy
let proxy = reqwest::Proxy::all("http://localhost:8080").unwrap();
let provider = yahoo::YahooConnector::builder()
.proxy(proxy)
.build()
.unwrap();
// Fully custom reqwest client. Note: with the `blocking` feature the
// builder accepts `reqwest::blocking::Client` instead
let client = reqwest::Client::builder().build().unwrap();
let provider = yahoo::YahooConnectorBuilder::build_with_client(client).unwrap();
}Time periods are given as strings, combined from the number of periods (except for "ytd" and "max") and a string label specifying a single period. The following period labels are supported:
| label | description |
|---|---|
| m | minute |
| h | hour |
| d | day |
| wk | week |
| mo | month |
| y | year |
| ytd | year-to-date |
| max | maximum |
Supported quote intervals for a given range:
| range | interval |
|---|---|
| 1d | 1m, 2m, 5m, 15m, 30m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo |
| 5d | 5m, 15m, 30m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo |
| 1mo | 2m, 3m, 5m, 15m, 30m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo |
| 3mo | 1h, 1d, 1wk, 1mo, 3mo |
| 6mo | 1h, 1d, 1wk, 1mo, 3mo |
| 1y | 1h, 1d, 1wk, 1mo, 3mo |
| 2y | 1h, 1d, 1wk, 1mo, 3mo |
| 5y | 1d, 1wk, 1mo, 3mo |
| 10y | 1d, 1wk, 1mo, 3mo |
| ytd | 1m, 2m, 5m, 15m, 30m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo |
| max | 1m, 2m, 5m, 15m, 30m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo |
Note: the table lists the historically accepted combinations. Intraday data
is additionally constrained by time — 1m is limited to roughly the last 8
days, 2m-90m to ~60 days, and 1h to the last 730 days. For the long
ranges (ytd, max) Yahoo in practice only serves daily and coarser
intervals (1d, 5d, 1wk, 1mo, 3mo) and rejects the rest with an
ApiError.
All methods return Result<_, YahooError>. The errors fall into a few categories:
- Transport:
ConnectionFailed(any reqwest error),FetchFailed,ServerError(5xx),NoResponse,InvalidUrl,InvalidDateFormat - Yahoo API:
ApiError,Unauthorized(also 403 — stale session),InvalidCrumb,InvalidCookie,NoCookies,TooManyRequests,InvisibleAsciiInCookies - Empty or inconsistent data:
NoResult,NoQuotes,DataInconsistency,MissingField,EmptyResponse(empty body),HtmlResponse(non-JSON HTML body) - Deserialization:
DeserializeFailed; with thedebugfeature,DeserializeFailedDebugincludes the response body (truncated) - Client setup:
BuilderFailed(reserved, currently not constructed)
Interested in contributing? See CONTRIBUTING.md for guidelines.
Licensed under either of Apache License, Version 2.0 or MIT license, at your option.