Tutorial

Live stock prices in Excel with Power Query and an API

The Stocks data type is great for a glance and awkward as a foundation. Power Query can call a real API with a header key, refresh on a timer, and put bid, ask, FX, crypto and history into ordinary tables.

On this page
  1. Excel stock data type, STOCKHISTORY or an API
  2. The two-minute version: one symbol from the From Web dialog
  3. A reusable workbook: one parameter, two functions, three sheets
  4. Daily history in its own sheet
  5. Refresh Excel stock prices on a timer without burning the quota
  6. Errors and what to do about them
  7. Questions

Key takeaways

  • Power Query's Web connector can call a REST API with an `x-api-key` header, parse the JSON and load it as an Excel table that refreshes on a timer.
  • Microsoft says Stocks data type information is delayed, and STOCKHISTORY returns daily, weekly or monthly bars only; an API adds bid, ask and intraday bars.
  • One parameter holds the key, one function fetches a snapshot, and one line per sheet turns a symbol table into a stocks, FX or crypto sheet.
  • A workbook refreshing every 5 minutes keeps calling while it is open, nights and weekends included, so price the refresh interval before you set it.

To get live Excel stock prices from an API, use Power Query's From Web connector: point it at a REST endpoint such as https://api.tickerlayer.com/stocks/snapshot/US:KO, send your key in an x-api-key header, load the JSON as a table, and set the query to refresh every few minutes. The same query shape works for currency pairs, crypto and commodities, and it reads exactly the numbers your code gets from the stock API.

Below: the one-symbol version you can do from a dialog in two minutes, then a reusable workbook with a key parameter, a snapshot function, separate stocks, FX and crypto sheets, a daily history sheet, and the refresh arithmetic. The menus are those of Excel for Windows with Microsoft 365; Excel for Mac offers fewer Get Data connectors, so check that From Web is there before you start.

Excel stock data type, STOCKHISTORY or an API

Excel already has two stock features. Type a ticker, choose Data > Stocks, and the cell becomes a data type with fields such as price, change and previous close. STOCKHISTORY returns historical bars into a range. Both are quick, and both are built for glancing at a portfolio: Microsoft's own note says the stock information is delayed and not for trading purposes, and STOCKHISTORY stops at daily, weekly and monthly intervals.

FeatureStocks data typeSTOCKHISTORYPower Query + API
SetupNoneOne formulaOne query per sheet
LatencyDelayed, per MicrosoftDaily historySame feed your code uses
Bid and ask with sizes
Intraday bars (1 minute to 4 hours)
Crypto, FX and commodities by code
Same numbers as your application
Refresh schedule you control
Built-in features as Microsoft documents them. The API column assumes a plan that includes each asset class.

The strongest reason is the last-but-one row. When a spreadsheet sits next to an application, a report or a model, it should show the same numbers from the same source. A finance team checking a dashboard against Excel should never have to explain a difference caused by two different feeds.

The two-minute version: one symbol from the From Web dialog

  1. Open the Web connectorData > Get Data > From Other Sources > From Web, then choose Advanced.
  2. Enter the URLURL parts: https://api.tickerlayer.com/stocks/snapshot/US:KO. Stocks always carry a market prefix; a bare KO returns 400.
  3. Add the headerUnder HTTP request header parameters type x-api-key on the left and your key on the right. Click OK.
  4. Connect anonymouslyWhen Excel asks how to authenticate, pick Anonymous. The key already travels in the header.
  5. Turn the record into a tablePower Query shows a record. Choose Into Table, then Close & Load. You now have a two-column table of field names and values.

A reusable workbook: one parameter, two functions, three sheets

  1. Symbol tableStockSymbols, FxSymbols, CryptoSymbols
  2. fnQuotesone row per symbol
  3. fnSnapshotWeb.Contents + x-api-key
  4. REST snapshot/{asset}/snapshot/{symbol}
  5. Loaded tableLast, Change %, Bid, Ask, time
Adding a symbol is typing it into a table and pressing Refresh All.
  1. Create the key parameterOpen Power Query (Data > Get Data > Launch Power Query Editor), then Home > Manage Parameters > New. Name TL_API_KEY, type Text, current value your key.
  2. Create the symbol tablesOn three sheets, type a header Symbol and a few symbols, then Insert > Table and name the tables StockSymbols, FxSymbols and CryptoSymbols in the Table Design tab.
  3. Add the two functionsIn Power Query choose Home > New Source > Other Sources > Blank Query, open Advanced Editor, paste each listing below, and name the queries fnSnapshot and fnQuotes.
  4. Add one query per sheetThree more blank queries, each a single line: = fnQuotes("StockSymbols", "stocks"), = fnQuotes("FxSymbols", "forex") and = fnQuotes("CryptoSymbols", "crypto"). Load each to its own sheet.
fnSnapshot (Power Query M)
(asset as text, symbol as text) as record =>
let
    Response = Web.Contents(
        "https://api.tickerlayer.com",
        [
            RelativePath = asset & "/snapshot/" & symbol,
            Headers = [#"x-api-key" = TL_API_KEY],
            // Read these statuses ourselves instead of failing the whole refresh
            ManualStatusHandling = {400, 401, 403, 404, 429}
        ]
    ),
    Status = Value.Metadata(Response)[Response.Status],
    Result =
        if Status = 200 then Json.Document(Response)
        else [symbol = symbol, error = "HTTP " & Text.From(Status)]
in
    Result
fnQuotes (Power Query M)
(tableName as text, asset as text) as table =>
let
    Source = Excel.CurrentWorkbook(){[Name = tableName]}[Content],
    Typed = Table.TransformColumnTypes(Source, {{"Symbol", type text}}),
    Symbols = Table.SelectRows(Typed, each [Symbol] <> null and Text.Trim([Symbol]) <> ""),
    WithData = Table.AddColumn(Symbols, "Data",
        each fnSnapshot(asset, Text.Upper(Text.Trim([Symbol])))),
    Expanded = Table.ExpandRecordColumn(WithData, "Data",
        {"last_price", "change_percent", "bid", "ask", "prev_close", "last_timestamp", "error"},
        {"Last", "Change %", "Bid", "Ask", "Prev close", "last_timestamp", "Error"}),
    // Unix milliseconds (UTC) to an Excel date-time
    WithTime = Table.AddColumn(Expanded, "Last trade (UTC)",
        each if [last_timestamp] = null then null
             else #datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [last_timestamp] / 1000),
        type datetime),
    // The API sends 0.3883 for +0.39%; Excel percent format wants 0.003883
    AsPercent = Table.TransformColumns(WithTime,
        {{"Change %", each if _ = null then null else _ / 100, Percentage.Type}}),
    Result = Table.RemoveColumns(AsPercent, {"last_timestamp"})
in
    Result

ManualStatusHandling is what keeps one typo from breaking the sheet: a 404 for an unknown symbol becomes a row with HTTP 404 in the Error column while the other rows load normally. RelativePath keeps the base URL constant, which is also what Power BI needs if you ever publish the same query there. The time conversion is the usual Unix-milliseconds arithmetic; the Unix timestamp guide covers the edge cases.

SheetSymbolLastChange %BidAskLast trade (UTC)
StocksUS:KO88.1510.39%88.1588.22026-09-28 10:56:49
FXUSDJPY157.092-0.09%157.0982157.10522026-09-28 10:25:14
CryptoBTCUSD82,820-1.96%82,82082,820.012026-09-28 10:25:13
One row from each sheet, built from snapshots captured on 2026-09-28. US:KO was in pre-market, so its last trade is a pre-market print.

FX pairs are six letters (EURUSD, USDJPY), crypto pairs are concatenated without a separator (BTCUSD, ETHUSD), and commodities use reference codes such as XAUUSD or NGASUSD. A fourth sheet for commodities is one more line: = fnQuotes("CommoditySymbols", "commodities"). For how FX quotes differ from a mid rate, see the exchange rate API guide.

Daily history in its own sheet

The snapshot answers "where is it now". For "how did it get here", call the bars endpoint, GET /stocks/agg/{symbol}/1/day/{from}/{to}, and turn its results array into a table. The same route serves 1, 5 and 15-minute, 1 and 4-hour, and daily bars, so an intraday sheet is the same query with 1/minute in the path.

KO daily (Power Query M)
let
    Response = Web.Contents(
        "https://api.tickerlayer.com",
        [
            RelativePath = "stocks/agg/US:KO/1/day/2026-01-02/2026-09-25",
            Query = [sort = "asc", limit = "5000"],
            Headers = [#"x-api-key" = TL_API_KEY]
        ]
    ),
    Body = Json.Document(Response),
    Bars = Table.FromRecords(Body[results]),
    Renamed = Table.RenameColumns(Bars,
        {{"o", "Open"}, {"h", "High"}, {"l", "Low"}, {"c", "Close"}, {"v", "Volume"}}),
    // Daily bars are stamped at 00:00 UTC of the session date
    WithDate = Table.AddColumn(Renamed, "Date",
        each Date.From(#datetime(1970, 1, 1, 0, 0, 0) + #duration(0, 0, 0, [t] / 1000)), type date),
    Result = Table.SelectColumns(WithDate, {"Date", "Open", "High", "Low", "Close", "Volume"})
in
    Result
DateOpenHighLowCloseVolume
2026-09-2188.3288.5087.1287.1218,212,480
2026-09-2287.6088.9787.45588.6117,355,834
2026-09-2388.9588.99587.60588.0913,105,701
2026-09-2488.8289.3688.1088.1013,252,762
2026-09-2588.1688.318387.55587.8112,261,067
Last five rows of the US:KO daily sheet, from the live API.

Query values must be text, hence limit = "5000". One page holds up to 5,000 bars, which is about twenty years of daily data; for minute bars, pass the response's next_offset back as offset until it is null. The historical stock data guide explains official closes, split adjustments and session boundaries.

Refresh Excel stock prices on a timer without burning the quota

  1. Open the query propertiesData > Queries & Connections, right-click a query, Properties.
  2. Pick an intervalTick Refresh every N minutes. Tick Refresh data when opening the file so a morning open starts fresh.
  3. Keep typing while it runsLeave Enable background refresh on so the grid stays usable during a refresh.

The timer is blunt: it fires whenever the workbook is open, at 3 a.m. and on Sundays too, and every refresh calls the API once per symbol. That makes the interval the biggest lever on your monthly request count.

WorkbookRefreshOpen forRequests a monthFits
10 stocksevery 60 min8 h x 21 days1,680Free tier (3,000)
10 stocksevery 5 min8 h x 21 days20,160Individual (250K)
10 stocks + 10 FX + 10 cryptoevery 5 min8 h x 21 days60,480Individual (250K)
30 symbols, left openevery 5 min24 h x 30 days259,200Business (25M)
Symbols x refreshes per hour x hours open x days. Refresh All refreshes every sheet at once.

The last row is the trap: the same 30-symbol workbook that fits an Individual plan during office hours overshoots it when someone leaves it open on a desktop over the month. Plan limits and the per-second ceiling are on the pricing page and in the limits docs.

Errors and what to do about them

You seeMeaningFix
HTTP 401 in the Error columnKey missing or wrongRe-enter TL_API_KEY; no quotes or spaces
HTTP 400Malformed symbolStocks need a prefix: US:KO, DE:SAP, JP:7203
HTTP 404Unknown symbol or wrong sheetCheck it with the symbol checker
HTTP 429Per-second limit or monthly quotaRefresh less often, or split a very large table
Formula.Firewall messagePrivacy levels block sending workbook data to a web sourceGive the web source a privacy level in Data Source Settings, or for a private workbook use Query Options > Privacy > Ignore the Privacy Levels
A credentials prompt on every refreshThe source was saved with the wrong methodData Source Settings > Edit Permissions > Anonymous

Questions

How do I get live stock prices in Excel?

Use Data > Get Data > From Web with a stock API URL and an x-api-key header, load the JSON as a table, and set the query to refresh every few minutes in its properties.

Is the Excel stock data type real time?

No. Microsoft states that stock information in the data type is delayed and not for trading purposes. Use an API through Power Query when you need current quotes or intraday bars.

Can Power Query send an API key in a header?

Yes. Web.Contents accepts a Headers record, for example [#"x-api-key" = TL_API_KEY], and the From Web dialog has an Advanced section for header parameters.

How do I get historical stock prices in Excel?

STOCKHISTORY covers daily, weekly and monthly bars. For intraday bars or non-stock assets, call a bars endpoint such as /stocks/agg/US:KO/1/day/{from}/{to} from Power Query and convert the Unix timestamps to dates.

Why does my Power Query show a Formula.Firewall error?

Power Query refuses to send data from the workbook, here your symbol list, to a web source when their privacy levels conflict. Set the web source's privacy level in Data Source Settings or, for a workbook only you use, choose Query Options > Current Workbook > Privacy > Ignore the Privacy Levels.

Keep reading

Ready to integrate?

Start with the free tier, explore the docs, and connect via REST or WebSocket in minutes.