sifting/io
Commodities
6 min readSiftingIO Team

Commodities market data API: backfill historical bars, then stream live prices

Pull historical OHLCV bars for gold, crude, and other commodities over REST, stream live prices over WebSocket, and handle the seam where they meet.

Commodities market data API: backfill historical bars, then stream live prices

A commodities market data API has to solve two problems that look similar but behave differently: backfilling enough history to power charts and backtests, and keeping prices current once that history is loaded. This guide walks through both with SiftingIO's commodities coverage. You'll pull historical OHLCV bars over REST, subscribe to live prices over WebSocket, and handle the seam where they meet, plus the session and calendar quirks that separate commodities from a 24/7 crypto feed.

Commodities coverage: symbols, fields, and history depth#

SiftingIO publishes spot pricing and OHLCV bars for precious metals, industrial metals, energy benchmarks, and agricultural products. Symbols are the commodity code plus USD, uppercase, no separator: XAUUSD for gold, XAGUSD for silver, XPTUSD for platinum, WTIUSD and BRENTUSD for the crude benchmarks, NATGASUSD for natural gas, COPPERUSD for copper, and CORNUSD for corn. The symbols page on the site lists the full catalog.

Each published price is a consensus value: a volume-and-reputation-weighted median aggregated across multiple independent venues rather than a pass-through of any single feed. That matters here because commodity spot markets are fragmented and a single source can go quiet without warning. Every tick carries a quality flag, so a degraded feed announces itself. Treat it as a reference price for research, dashboards, and validation rather than an execution feed.

History depth follows the plan tier: a month on the free tier, a year on Builder, and the full archive on Pro and above. Current limits are on the pricing page.

Backfilling historical bars over REST#

Historical bars live under /v1/hist. For commodities the request looks like this:

curl --compressed -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/hist/commodities/XAUUSD/bars?interval=1d&limit=2000"

Two details in that command are load-bearing. Authentication is the X-API-Key header (there is no Bearer auth), and, compressed makes curl send Accept-Encoding: gzip. Historical bar endpoints require gzip; without it the API returns a 406 with {"error":"gzip_required"} and no data.

Pagination is cursor-based. limit defaults to 1000 and caps at 2000 bars per page, and each response's meta.next_cursor points to the next page, going null on the last one. A full backfill is a loop:

import os
import requests

def backfill(symbol, interval="1d"):
    url = f"https://api.sifting.io/v1/hist/commodities/{symbol}/bars"
    headers = {"X-API-Key": os.environ["SIFTING_KEY"]}
    params = {"interval": interval, "limit": 2000}
    bars = []
    while True:
        resp = requests.get(url, headers=headers, params=params, timeout=30)
        resp.raise_for_status()
        payload = resp.json()
        bars.extend(payload["data"])
        cursor = payload["meta"]["next_cursor"]
        if cursor is None:
            return bars
        params["cursor"] = cursor

xau_daily = backfill("XAUUSD")

The requests library negotiates gzip on its own, so no extra header is needed there. Each bar is an OHLCV object with a UTC timestamp; sort by time before computing anything, since page order isn't guaranteed to be chronological.

Real-time commodities prices: snapshot or stream#

For a dashboard that refreshes every few seconds, the REST snapshots may be all you need:

curl -H "X-API-Key: $SIFTING_KEY" \
  "https://api.sifting.io/v1/last/quote/commodities/WTIUSD"

/v1/last/quote returns the best bid and ask, and /v1/last/trade/commodities/{symbol} the latest trade with price, size, and timestamp. Polling fits inside REST rate limits at low symbol counts.

For continuous updates, connect to the WebSocket stream at wss://stream.sifting.io/ws/v1?key=$SIFTING_KEY. The key travels in the query string, or in an {"op":"auth","key":"sft_..."} frame after connecting; the server acks with your tier. Subscribing is one frame. The product code for commodities is com, the same short code that tags commodity ticks; the stream's product codes are not the REST slugs, so it is com here, not commodities:

{ "op": "subscribe", "product": "com", "symbols": ["XAUUSD", "WTIUSD"] }

Updates arrive as tick frames:

{ "f": "tick", "class": "com", "s": "XAUUSD", "p": 3341.2, "b": 3341.0, "a": 3341.4, "t": 1778019852426 }

class is the venue type (com for commodities), p is the aggregated price, b and a are bid and ask, and t is Unix epoch milliseconds. Two protocol details matter. On subscribe, the server first replays the last cached value, so a price shows up before the market next ticks. And it closes any connection that sends no client frames for 90 seconds, so send {"op":"ping"} at least every 60. Update cadence is tier-dependent, from 1 Hz on the free tier to 10 Hz on Ultra; that's a reference-price update rate rather than execution latency.

Stitching history to the live stream#

The seam between the last historical bar and the first live tick is where integrations quietly go wrong. The order that works: backfill over REST first, then open the stream. The replayed cached value gives you a price immediately, the REST archive stays the source of truth for closed periods, and the only ambiguous region left is the current, still-forming bar.

Treat that bar as mutable. The most recent bar REST returns during an open session covers a period that hasn't closed, so don't persist it as final. Bucket each incoming tick by flooring its timestamp to the interval (for hourly bars, bucket = t // 3_600_000). While a tick's bucket matches the latest bar's bucket, update the bar in place: raise the high, lower the low, set close to p. When a tick lands in a later bucket, seal the old bar and open a new one seeded from that tick.

Deduplication falls out of the same arithmetic: a tick whose bucket is older than your latest bar is history you already hold from REST, so drop it. That rule also absorbs the replayed cached value, whether it predates or postdates the backfill. Reconnects are the same seam again. Subscriptions don't survive a dropped connection, so resubscribe, let the bucket comparison sort the replayed value, and re-run the backfill for the gap if the outage was long.

Common pitfalls#

Sessions are per product, and none of them run 24/7. Metals and energy pairs trade nearly around the clock on weekdays but stop for the weekend, while agricultural products keep their own shorter sessions. Don't hard-code a schedule. GET /v1/fnd/markets returns the market catalog, /v1/fnd/markets/{market}/status reports open or closed right now, and the /hours and /calendar endpoints publish each market's weekly schedule and holidays. A poller that checks status before hammering /v1/last spends its rate budget only on hours that matter. Related: a /v1/last request during a closed session can return 503 stale_snapshot, which means the newest data is older than the freshness threshold; the body includes last_t and server_now so code can decide whether Friday's close is acceptable.

Units differ by an order of magnitude or three. XAUUSD quotes dollars per troy ounce and WTIUSD dollars per barrel, so a chart that plots both on one axis flattens crude into a line along the bottom. Normalize to percentage change or give each series its own axis, and never mix units when summing exposure across commodities.

Weekend and holiday gaps break naive time-series code. A daily series for CORNUSD has no rows for closed days, so reindexing to a continuous calendar in pandas fills gaps with NaN, and a return computed from Friday's close to the next session's close spans several calendar days. Compute returns on trading days, and when a model needs calendar alignment across commodities, align on each market's own calendar from the API instead of assuming they share one.

That's the whole loop: backfill bars over REST with gzip and cursors, stream ticks with a ping every minute, merge at the bucket boundary, and let the market-hours endpoints tell you when prices should and shouldn't be moving. The endpoint reference, streaming product list, and per-plan history depth are in the docs. Read the docs

Keep reading

Related posts

How to Get Gold and Oil Spot Prices via API
Commodities4 min read

How to Get Gold and Oil Spot Prices via API

Gold and oil spot prices from one API call. How SiftingIO symbols commodities (XAUUSD, WTIUSD), how to read a last trade or quote, and how the published price is aggregated across venues.

SiftingIO Team
What are the 7 C commodities?
Commodities4 min read

What are the 7 C commodities?

The 7 C commodities are coffee, corn, cotton, copper, crude oil, cocoa, and cattle. A plain guide to what the seven mean, why they are grouped, and how to track their prices.

SiftingIO Team