How do you get crude oil prices into an app through an API, and which oil benchmark does the app actually need? Most feeds answer "oil" with a single number, but two benchmarks matter to a developer: Brent crude, the waterborne North Sea benchmark, and WTI crude, the US benchmark. SiftingIO publishes both, as UKOUSD and WTIUSD, with historical OHLCV bars and real-time prices behind one API key. This post covers what each benchmark measures, why the two prices differ, and the exact calls for backfilling history and streaming live updates.
Brent and WTI: two benchmarks, two prices#
Brent crude prices light, sweet crude loaded from fields in the North Sea. Brent cargoes travel by sea, so they can be delivered almost anywhere, and that mobility is why a large share of internationally traded crude is priced against Brent. If an app serves users in Europe, Africa, or most of Asia, the oil number those users see in the news is usually Brent. On SiftingIO the symbol is UKOUSD.
WTI (West Texas Intermediate) prices US light, sweet crude delivered inland, at the storage hub in Cushing, Oklahoma. It's a landlocked benchmark: on top of global supply and demand, its price reflects US production, pipeline capacity into and out of the hub, and inventory levels there. US-focused products, fuel-price trackers, and anything tied to the domestic energy market typically reference WTI. The SiftingIO symbol is WTIUSD.
The two prices track each other, but they aren't interchangeable. The benchmarks have different delivery points, different logistics, and slightly different crude grades, so they trade at a spread. That gap widens and narrows with shipping costs, regional supply disruptions, and inventory conditions; over the past two decades it has been nearly zero at times, double digits at others, and it has changed sign. None of that is a forecast. It's the structural reason an app shouldn't display one benchmark and label it "oil price" for a global audience. Energy dashboards commonly show both, and often chart the Brent-WTI spread as its own series.
A naming note that saves a 404: BRENTUSD is not a valid SiftingIO symbol. Brent is UKOUSD (UK oil; commodity codes follow the code-plus-USD pattern, like XAUUSD for gold). US crude has no USOUSD either; the code is WTIUSD. Requests for a guessed symbol return 404 unknown_ticker. The full commodities list, covering metals, energy, and agricultural products, is on the commodities product page.
Historical crude oil prices: backfilling OHLCV bars#
Historical bars for both benchmarks live under /v1/hist/commodities/{symbol}/bars. One thing to know before the first request: historical bar endpoints require gzip. Without an Accept-Encoding: gzip header the API returns 406 gzip_required rather than silently sending an uncompressed payload. With curl, --compressed sets the header and inflates the response:
curl --compressed -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/hist/commodities/UKOUSD/bars?interval=1d&limit=1000"
Pagination is cursor based. Each page carries meta.next_cursor; pass it back as ?cursor= until it comes back null. Page size defaults to 1000 bars and caps at 2000, so a multi-year daily backfill is a short loop rather than one giant request. In Python, requests negotiates gzip automatically:
import os
import requests
BASE = "https://api.sifting.io/v1"
HEADERS = {"X-API-Key": os.environ["SIFTING_KEY"]}
def bars(symbol, interval="1d"):
params, out = {"interval": interval, "limit": 1000}, []
while True:
r = requests.get(f"{BASE}/hist/commodities/{symbol}/bars",
headers=HEADERS, params=params)
r.raise_for_status()
body = r.json()
out.extend(body["data"])
if body["meta"]["next_cursor"] is None:
return out
params["cursor"] = body["meta"]["next_cursor"]
brent = bars("UKOUSD")
wti = bars("WTIUSD")
Timestamps are UTC, which matters when the two series meet. Both benchmarks arrive on the same clock, so aligning daily Brent and WTI closes to chart the spread is a plain join on the bar timestamp, with no session-calendar arithmetic. Intervals run from one minute upward; check /docs for the full interval list and how far back history extends on your plan. The free tier includes one month of history, and paid tiers extend that to a year or to full history.
Live crude oil prices: REST snapshot or WebSocket stream#
For a price that refreshes on page load, or a job that samples every few minutes, the snapshot endpoints are enough. The venue segment for both symbols is commodities:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/trade/commodities/WTIUSD"
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/quote/commodities/UKOUSD"
last/trade returns the latest price with size and timestamp; last/quote returns the best bid and ask. The number is SiftingIO's aggregated fair price, a consensus formed across multiple independent venues rather than a single source's print. That makes it a reference value for display, analytics, and validation; it is not an execution feed.
For a live-updating chart or an alerting service, polling wastes the request budget. The WebSocket stream pushes ticks instead:
const ws = new WebSocket(
`wss://stream.sifting.io/ws/v1?key=${process.env.SIFTING_KEY}`
);
ws.onopen = () => {
ws.send(JSON.stringify({
op: "subscribe",
product: "com",
symbols: ["UKOUSD", "WTIUSD"],
}));
setInterval(() => ws.send(JSON.stringify({ op: "ping" })), 60_000);
};
ws.onmessage = (event) => {
const frame = JSON.parse(event.data);
if (frame.f === "tick") {
// frame.s: "UKOUSD" or "WTIUSD"
// frame.p: price, frame.b / frame.a: bid and ask
// frame.t: Unix epoch milliseconds
update(frame.s, frame.p, frame.t);
}
};
Note the subscribe uses com, not commodities: the stream's product code for commodities is com, even though the REST venue slug is commodities. Two behaviors are worth designing around. First, on subscribe the server immediately sends the last cached tick for each symbol before live updates begin, so the UI paints instantly instead of waiting for the next market print. Second, tick frequency is tier dependent: the fair price updates at 1 Hz on the free tier and up to 10 Hz on Ultra. Those are fair-price update rates, not an execution-latency figure. If a subscribe returns {"f":"error","code":"unknown_product"}, the product slug is not one the stream recognizes; the current product list is in /docs.
Common pitfalls#
The 406 gzip_required response on bars is the one that surprises people first. It is the documented contract on heavy endpoints, and the fix is one flag: add --compressed in curl, or keep default settings in requests, which sends Accept-Encoding: gzip on its own.
Guessed symbols are the second. BRENTUSD, USOUSD, and OILUSD all return 404 unknown_ticker. The only two crude benchmarks in the catalog are UKOUSD and WTIUSD, and symbol codes are uppercase with no separators.
Silent WebSocket drops are the third. The server closes any connection that sends no client frames for 90 seconds, so a listener that only reads will die quietly. Send {"op":"ping"} at least every 60 seconds. On reconnect, resubscribe and expect a cached tick first; deduplicate by the t timestamp if the consumer is append-only.
Which benchmark should the app show#
The audience decides, not the API: WTI for US-facing products, Brent for international ones, and both plus the spread for anything aimed at people who follow the energy market. Both symbols share one endpoint family, one message format, and one API key, so supporting the second benchmark is a one-line change. The free tier includes both symbols with a month of history and a live WebSocket connection, no credit card required. Start building free



