Stream live prices over a WebSocket

Open one WebSocket to wss://api.narwhalapi.com/v1/prices/stream with your key in the Authorization header, send a subscribe message within 5 seconds, and read price messages as they arrive. Streams are on Basic (2 connections) and Pro (10); Free has none.

Subscribe to prices with the API

The prices stream carries gold (XAU), silver (XAG), platinum (XPT) and palladium (XPD), plus FX pairs grouped by base currency and quote list. Oil (BRENT, WTI) and copper (XCU) are available only through the REST spot endpoints. A stream subscription for those commodities returns commodity_not_supported.

  1. Prepare Python and your API key Install Python's websockets package with pip install "websockets>=14". Version 14 or later supports the additional_headers argument used here. Keep your key in the NARWHAL_API_KEY environment variable. Narwhal uses one account and one API key across its data APIs, and REST and MCP share that key. You can get a free key, though streaming requires Basic or Pro.
  2. Connect and send your subscription Servers and apps send Authorization: Bearer YOUR_KEY in the WebSocket handshake headers; stream tickets are for the Narwhal website only. Read ready, then send one subscribe message within 5 seconds. This script requests XAU in USD and the EUR and JPY quotes for a USD base. Put the instruments you want into that single subscription. The script reads the key from the environment and prints each decoded message. The header goes into additional_headers when the socket opens; the subscribe message goes through ws.send after the initial ws.recv.
    Python (websockets), subscribe to gold and two dollar pairs
    import asyncio, json, os
    import websockets
    
    async def main():
        headers = {"Authorization": f"Bearer {os.environ['NARWHAL_API_KEY']}"}
        async with websockets.connect("wss://api.narwhalapi.com/v1/prices/stream", additional_headers=headers) as ws:
            print(json.loads(await ws.recv()))                       # ready
            await ws.send(json.dumps({"action": "subscribe",
                                      "commodities": [{"commodity": "XAU", "currency": "USD"}],
                                      "fx": [{"base": "USD", "currencies": ["EUR", "JPY"]}]}))
            async for message in ws:
                print(json.loads(message))
    
    asyncio.run(main())
  3. Read messages and handle errors Read continuously after subscribing. Message types are ready, subscribed, commodity, fx, status, heartbeat, pong and error. Price messages have the same body as the REST endpoints, with a type field added. A second subscribe message returns resubscribe_required, so reconnect when you need to change the subscription. Each connection allows up to 30 frames; the first message can be up to 8,192 bytes and later messages up to 2,048 bytes.

For a commodity message, symbol identifies the commodity and currency, while unit identifies the price unit. An FX message names base and currency alongside rate. Both message bodies include a timestamp and stale field. Use the message's type to distinguish commodity prices from FX rates in the receive loop.

Stream errors include unauthorized, plan_upgrade_required, stream_limit, quota_unavailable, subscribe_required, resubscribe_required, invalid_command, invalid_frame, commodity_not_supported, currency_not_supported, revoked, slow_consumer, command_limit and unavailable. Free has no stream access: a handshake with a Free key is refused or closed with policy violation code 1008.

Use a schedule without code

For a no-code flow, use a schedule plus an HTTP request. No-code tools poll rather than stream, so use a scheduled REST pull for an end-of-day report. The commodity price alert tutorial is another route when your task is an alert on a price level. Polling and streaming are both options for that task. A scheduled REST request also covers commodities outside the stream, such as oil and copper. For those instruments, use the REST spot endpoints even when the rest of your application reads metals over a WebSocket.

If you want to watch prices without building a flow, the Narwhal website's price pages show prices updating. For an application that needs price ticks, connect to the stream with Basic or Pro. The REST API base URL is https://api.narwhalapi.com, and HTTP requests use Authorization: Bearer YOUR_KEY with the same key kept in NARWHAL_API_KEY.

Which option fits your price task

SituationBest optionWhy
A dashboard or bot that needs ticksPrices stream on Basic or ProRead price messages as they arrive
An end-of-day reportScheduled REST pullUse a schedule and an HTTP request
An alert on a price levelPoll or streamEither route can support a level alert

What happens when the connection drops?

Reconnect with backoff and subscribe again to receive the current prices for every subscribed instrument. Missed changes are not replayed. A reconnect sends every subscribed price again, so the first prices after reconnecting can include instruments you had already received before the connection dropped.

The other streams, /v1/events/stream and /v1/predictions/stream, connect with the same header-based authentication. The events stream carries delivery events and has a replayable REST journal.

Why was I disconnected after 5 seconds?

A connection closes if no subscribe message arrives within 5 seconds after ready, or if the client stops reading for 5 seconds. The subscription deadline produces subscribe_required; a client that stops reading receives slow_consumer. The receive loop in the Python script reads messages continuously after it sends the subscription.

Does streaming cost quota?

Each connection uses one call from the monthly quota. The prices, events and predictions streams share the concurrent connection limit, so count connections across those streams together. A prices connection and an events connection both occupy that shared allowance. See plans and limits when deciding how many connections your application needs.

Worked example: gold and two dollar pairs

Subscribe to XAU/USD, USD/EUR and USD/JPY in one message, as in the Python script. First read ready, then send the subscription. The stream sends subscribed, followed by one price message per instrument, then updates. The commodities entry identifies gold and its currency; the FX entry groups the EUR and JPY quote currencies under USD.

On 11 October 2026, the gold message at 06:25:00Z has a price of 4195.86 USD per troy ounce. On 9 October 2026, the USD/EUR message at 00:00:00Z has a rate of 0.8924. Each message carries its own timestamp and stale field alongside the price or rate.

Gold message dated 11 October 2026
{"type":"commodity","symbol":"XAU-USD","price":"4195.86","currency":"USD","unit":"troy_ounce","timestamp":"2026-10-11T06:25:00Z","stale":false}
USD/EUR message dated 9 October 2026
{"type":"fx","base":"USD","currency":"EUR","rate":"0.8924","timestamp":"2026-10-09T00:00:00Z","stale":false}

Published .