Yahoo Finance API - GET quotes returns "Invalid Cookie"

17.4k Views Asked by At

For the past few years, I've been using the following link to fetch a full current quote for the symbols in the query:

https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL

All of a sudden, as of about 5 hours ago, I'm now getting the error response 'Unauthorised - Invalid Cookie' on every single device I call it from. Sometimes, I get the error response 'Unauthorised - Invalid Crumb'.

The historic data fetching still works fine:

(https://query1.finance.yahoo.com/v8/finance/chart/AAPL?metrics=high?&interval=1d&range=5d).

Is anyone else having this issue and/or knows how I can fix it??? Or have Yahoo discontinued this endpoint without telling anyone??

9

There are 9 best solutions below

6
JollyPotato On BEST ANSWER

I had the same issue today. Change the "v7" to "v6" in the URL in your GET request. It will then work, at least for now. Because the Yahoo Finance API hasn't been officially supported since 2017, we do what we can with it.

5
papa_k On

I just checked and v7 is now working again.

3
emoreau99 On

that might explain why it isn't working anymore!

enter image description here

0
TheKrush On

If you try to go to a quote api endpoint such as: https://query2.finance.yahoo.com/v7/finance/quote?symbols=AAL

You end up getting this: {"finance":{"result":null,"error":{"code":"Unauthorized","description":"Invalid Crumb. For Developers - https://docs.google.com/forms/d/e/1FAIpQLSeyb7xMtZFjoNYI7XG1rjlhUopKAxdLAfSRcaPxg9p-9ii-_g/viewform?fbzx=-7189957683680596558"}}} (i replaced the bitly link for posting)

Yahoo Finance | API Feedback

Where they explain how they've disabled access to quote data.

The v6 endpoint was killed off entirely.

2
rabinnh On

The only option that I've found is to use a different service. I have a paid subscription to Financial Modeling Plus (https://site.financialmodelingprep.com/developer/docs/pricing/)

They have a free tier that provides 250 quotes a day. That might be adequate for some people that only want daily updates for their portfolio.

1
bugflux On

I came across the same problem and switched to using the https://query1.finance.yahoo.com/v8/finance/chart/{ticker} endpoint which seems to work. The response format is slightly different but seems to contain similar information.

E.g. to get the current price:

Old endpoint: quoteResponse.result[0].regularMarketPrice

New endpoint: chart.result[0].meta.regularMarketPrice

1
Nathan On

An alternative I found on https://cryptocointracker.com/yahoo-finance/yahoo-finance-api#26cc3f3f62db4567857919c2653fab33 is to use https://query1.finance.yahoo.com/v7/finance/options/{symbol}
It has quote value-pair containing:

{
   "quote":{
      "language":"en-US",
      "region":"US",
      "quoteType":"EQUITY",
      "typeDisp":"Equity",
      "quoteSourceName":"Delayed Quote",
      "triggerable":true,
      "customPriceAlertConfidence":"HIGH",
      "exchange":"NMS",
      "currency":"USD",
      "earningsTimestamp":1691092800,
      "earningsTimestampStart":1691092800,
      "earningsTimestampEnd":1691092800,
      "trailingAnnualDividendRate":0.92,
      "trailingPE":32.131535,
      "dividendRate":0.96,
      "trailingAnnualDividendYield":0.004847974,
      "dividendYield":0.51,
      "epsTrailingTwelveMonths":5.93,
      "epsForward":6.58,
      "epsCurrentYear":5.98,
      "priceEpsCurrentYear":31.862875,
      "sharesOutstanding":15728700416,
      "bookValue":3.953,
      "fiftyDayAverage":180.4268,
      "fiftyDayAverageChange":10.11319,
      "fiftyDayAverageChangePercent":0.05605148,
      "twoHundredDayAverage":156.0571,
      "twoHundredDayAverageChange":34.482895,
      "marketCap":2996946599936,
      "twoHundredDayAverageChangePercent":0.22096333,
      "forwardPE":28.957447,
      "priceToBook":48.201363,
      "sourceInterval":15,
      "exchangeDataDelayedBy":0,
      "averageAnalystRating":"2.0 - Buy",
      "shortName":"Apple Inc.",
      "longName":"Apple Inc.",
      "messageBoardId":"finmb_24937",
      "exchangeTimezoneName":"America/New_York",
      "exchangeTimezoneShortName":"EDT",
      "gmtOffSetMilliseconds":-14400000,
      "market":"us_market",
      "esgPopulated":false,
      "marketState":"POSTPOST",
      "regularMarketChangePercent":0.40574852,
      "regularMarketPrice":190.54,
      "tradeable":false,
      "cryptoTradeable":false,
      "firstTradeDateMilliseconds":345479400000,
      "priceHint":2,
      "postMarketChangePercent":-0.11545396,
      "postMarketTime":1689292799,
      "postMarketPrice":190.32,
      "postMarketChange":-0.21998596,
      "regularMarketChange":0.769989,
      "regularMarketTime":1689278404,
      "regularMarketDayHigh":191.19,
      "regularMarketDayRange":"189.78 - 191.19",
      "regularMarketDayLow":189.78,
      "regularMarketVolume":38321242,
      "regularMarketPreviousClose":189.77,
      "bid":190.34,
      "ask":190.38,
      "bidSize":10,
      "askSize":10,
      "fullExchangeName":"NasdaqGS",
      "financialCurrency":"USD",
      "regularMarketOpen":190.5,
      "averageDailyVolume3Month":56677663,
      "averageDailyVolume10Day":51027070,
      "fiftyTwoWeekLowChange":66.369995,
      "fiftyTwoWeekLowChangePercent":0.5345091,
      "fiftyTwoWeekRange":"124.17 - 194.48",
      "fiftyTwoWeekHighChange":-3.9400024,
      "fiftyTwoWeekHighChangePercent":-0.020259166,
      "fiftyTwoWeekLow":124.17,
      "fiftyTwoWeekHigh":194.48,
      "fiftyTwoWeekChangePercent":26.882862,
      "dividendDate":1684368000,
      "displayName":"Apple",
      "symbol":"AAPL"
   }
}
1
dayfine On

I suspect there are some rate limiting at work. If you are making too many calls too quickly, the endpoint will treat the caller as an automated program instead of a human users. I started getting a lot of Unauthorized after calling one of the endpoints a few thousand times. Tried again after a few minutes and the request went through.

0
D A On

This is my working version that I am using it in a .Net 7 API project. I've solved the problem with the cookie and crumb by keeping them in cache and I have a sync/retry mechanism when the cache expires. This works well for 1 server instance but for scale situation it should be rethink. This is using Yahoo V10 without problems. There is no requests limiter here but I advise you to use one. In my case I have something build in UI (not very efficient, but is good enough for my case).

using Microsoft.Extensions.Caching.Memory;
using System.Net;

namespace WorkbenchAPI.Features.ServiceDelivery.StockMarket.SymbolsParsers
{
   public class YahooManager
   {
    private static readonly object yahooCredentialsLocker = new object();

    private const string STOCK_MARKET_URL_SUMMARY = "https://query1.finance.yahoo.com/v10/finance/quoteSummary/{0}?formatted=true&lang=en-US&region=US&modules=assetProfile%2CbalanceSheetHistory%2CbalanceSheetHistoryQuarterly%2CcalendarEvents%2CcashflowStatementHistory%2CcashflowStatementHistoryQuarterly%2CdefaultKeyStatistics%2Cearnings%2CearningsHistory%2CearningsTrend%2CesgScores%2CfinancialData%2CfundOwnership%2CincomeStatementHistory%2CincomeStatementHistoryQuarterly%2CindexTrend%2CindustryTrend%2CinsiderHolders%2CinsiderTransactions%2CinstitutionOwnership%2CmajorDirectHolders%2CmajorHoldersBreakdown%2CnetSharePurchaseActivity%2Cprice%2CrecommendationTrend%2CsecFilings%2CsectorTrend%2CsummaryDetail%2CsummaryProfile%2CupgradeDowngradeHistory%2Cpageviews%2Cquotetype&ssl=true";

    private const string YahooFcUrl = "https://fc.yahoo.com";
    private const string YahooGetCrumbUrl = "https://query2.finance.yahoo.com/v1/test/getcrumb";
    private const string CacheKeyCookieContainer = "YahooCookieContainer";
    private const string CacheKeyCrumb = "YahooCrumb";

    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IMemoryCache _memCache;

    public YahooManager(IHttpClientFactory httpClientFactory, IMemoryCache memCache)
    {
        _httpClientFactory = httpClientFactory;
        _memCache = memCache;
    }


    public async Task<string> GetYahooSymbolSummay(string symbol)
    {
        return await WebRequestGet(string.Format(STOCK_MARKET_URL_SUMMARY, symbol));
    }

    public async Task<string> WebRequestGet(string url)
    {
        (CookieContainer cookie, string crumb) credentials = new(null, null);
        lock (yahooCredentialsLocker)
        {
            int tryCounter = 0;
            while ((credentials.crumb == null || credentials.cookie == null) && tryCounter < 10)
            {
                Task.Delay(1000 * tryCounter).Wait();
                credentials = GetYahooCookie().ConfigureAwait(false).GetAwaiter().GetResult();
                tryCounter++;
            }//while
        }//lock

        string sAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36";
        var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, url + "&crumb=" + credentials.crumb)
        {
            Headers =
                        {
                            { "Cookie",  credentials.cookie.GetCookieHeader(new Uri(YahooFcUrl))},
                            { "User-Agent",sAgent }
                        }
        };
        var httpClient = _httpClientFactory.CreateClient();
        var webResponse = await httpClient.SendAsync(httpRequestMessage);
        //webResponse.EnsureSuccessStatusCode();
        string responseContent = await webResponse.Content.ReadAsStringAsync();
        return responseContent;
    }//WebRequestGet

    private async Task<(CookieContainer cookie, string crumb)> GetYahooCookie()
    {
        CookieContainer m_cookieContainer = null;
        string m_crumb = null;
        _memCache.TryGetValue(CacheKeyCookieContainer, out m_cookieContainer);
        _memCache.TryGetValue(CacheKeyCrumb, out m_crumb);

        if (m_cookieContainer == null || m_crumb == null)
        {               
            string sAgent = $"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36";

            m_cookieContainer = new CookieContainer();
            var uri = new Uri(YahooFcUrl);
            var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, YahooFcUrl)
            {
                Headers =
                        {
                            { "User-Agent", sAgent }
                        }
            };
            var httpClient = _httpClientFactory.CreateClient();
            var httpResponseMessage = await httpClient.SendAsync(httpRequestMessage);
            IEnumerable<string> cookies;
            httpResponseMessage.Headers.TryGetValues("Set-Cookie", out cookies);
            if (cookies == null)                                    
                return (null, null);
            
            foreach (var cookieValue in cookies)
                m_cookieContainer.SetCookies(uri, cookieValue);
            httpResponseMessage.Dispose();

            httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, YahooGetCrumbUrl)
            {
                Headers =
                        {
                            { "Cookie",  m_cookieContainer.GetCookieHeader(uri) },
                            { "User-Agent",sAgent}
                        }
            };
            httpClient = _httpClientFactory.CreateClient();
            var crumbResponse = await httpClient.SendAsync(httpRequestMessage);
            crumbResponse.EnsureSuccessStatusCode();
            string responseContent = await crumbResponse.Content.ReadAsStringAsync();
            m_crumb = responseContent.Trim('"');

            var cacheEntryOptions = new MemoryCacheEntryOptions()
                .SetSlidingExpiration(TimeSpan.FromSeconds(60*5))
                .SetAbsoluteExpiration(TimeSpan.FromSeconds(60*10))
                .SetPriority(CacheItemPriority.Normal);
            _memCache.Set(CacheKeyCookieContainer, m_cookieContainer, cacheEntryOptions);
            _memCache.Set(CacheKeyCrumb, m_crumb, cacheEntryOptions);
        }//endif
        return (m_cookieContainer, m_crumb);
    }//GetYahooCookie


  }
}