Get Latest Prices
Status: ✅ Available | 🕒 Source timestamps | 📊 Catalog endpoint
Returns the latest available price for a commodity, with source and freshness context when available. Call with a single by_code, or pass a comma-separated list to get several at once. Called with no by_code, it returns a single default commodity (BRENT_CRUDE_USD) — not every commodity.
Authentication
See Authentication Guide for API key setup.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
by_code | string | No | Commodity code, or several comma-separated (e.g. WTI_USD,BRENT_CRUDE_USD). Omit to get the default code. |
Response
Success (200) — Single commodity
data is a flat price object (not nested by commodity code):
{
"status": "success",
"data": {
"price": 68.58,
"formatted": "$68.58",
"currency": "USD",
"code": "WTI_USD",
"created_at": "2026-07-03T13:43:01.099Z",
"updated_at": "2026-07-03T13:43:01.099Z",
"type": "spot_price",
"unit": "barrel",
"source": "market_reporting",
"data_status": "current",
"freshness": {
"status": "current",
"age_seconds": 275,
"expected_max_age_seconds": 1800
},
"changes": {
"24h": {
"amount": 0.72,
"percent": 1.06,
"previous_price": 67.86,
"previous_timestamp": "2026-07-02T13:43:01Z",
"measured_at": "2026-07-03T13:43:01Z",
"span_hours": 24.0
},
"7d": { "amount": -1.01, "percent": -1.45, "previous_price": 69.59 },
"30d": { "amount": -26.41, "percent": -27.8, "previous_price": 94.99 },
"90d": { "amount": -42.96, "percent": -38.52, "previous_price": 111.54 }
},
"metadata": {
"source": "market_reporting",
"source_description": "Investing.com"
}
}
}
Success (200) — Multiple commodities
When you pass several codes, prices come back as an array under data.prices (not keyed by code), alongside a data.metadata block:
{
"status": "success",
"data": {
"prices": [
{
"price": 68.58,
"code": "WTI_USD",
"formatted": "$68.58",
"currency": "USD",
"type": "spot_price",
"unit": "barrel",
"data_status": "current"
},
{
"price": 71.98,
"code": "BRENT_CRUDE_USD",
"formatted": "$71.98",
"currency": "USD",
"type": "spot_price",
"unit": "barrel",
"data_status": "current"
}
],
"metadata": {
"request_id": "6390b34903a8d3f0",
"timestamp": "2026-07-03T13:47:37Z",
"version": "v1"
}
}
}
Response Fields
Note: For a single code the price object sits directly in
data. For multiple codes the objects are in thedata.pricesarray.
| Field | Type | Description |
|---|---|---|
price | number | Current price value (rounded to 2 decimals) |
formatted | string | Price formatted with currency symbol (e.g., "$68.58") |
currency | string | Price currency code (USD, EUR, GBP) |
code | string | Commodity code (e.g., "WTI_USD") |
created_at | string | ISO 8601 timestamp with milliseconds (UTC) |
observed_at | string | ISO 8601 source observation timestamp when available; for source-dated daily data this is the reference date |
source_date | string | Date-only source reference date when available, e.g. OPEC Basket daily values |
updated_at | string | ISO 8601 timestamp of the last update (UTC) |
type | string | Price type, e.g. "spot_price" |
unit | string | Unit of measure (e.g. "barrel") |
source | string | Data source identifier |
data_status | string | Freshness label, e.g. "current" |
freshness | object | status, age_seconds, expected_max_age_seconds — how recent the point is |
changes | object | Price moves keyed by window (24h, 7d, 30d, 90d) — see Price changes below |
metadata.source | string | Data source identifier |
metadata.source_description | string | Human-readable description of the data source |
Price changes
Every /v1/prices/latest response carries a changes object. There is no parameter to request it — it is always included, and it costs no additional API call.
Each window is an object:
| Field | Type | Description |
|---|---|---|
amount | number | Absolute move in the price currency (e.g. 0.72 = up $0.72) |
percent | number | Percentage move (e.g. 1.06 = up 1.06%) |
previous_price | number | The price compared against |
previous_timestamp | string | ISO 8601 timestamp of that earlier price |
measured_at | string | ISO 8601 timestamp of the current price |
span_hours | number | Hours actually spanned between the two points — may exceed the window label |
const pct = response.data.changes["24h"].percent; // 1.06
span_hours — read this before rendering a daily ticker
A 24h change legitimately spans longer than 24 hours when the market was closed. A Monday quote compares against Friday, so span_hours will read around 72.0. This is correct, not an error — we publish the span we actually measured rather than asserting the label, so you can decide whether to render it.
If you display a "today's move" figure, check span_hours before labelling it as such.
A window may be absent — check before reading it
When no honest comparison exists, the window is omitted rather than reported as zero. This happens when no price was published within the permitted lookback (for 24h, a generous multi-day allowance that absorbs weekends and holidays, but not a weekly-published series), and on the rare occasion a stale-price fallback is served — in that case the entire changes object is absent.
Omission is deliberate: a fabricated percentage is worse than no percentage. Sparsely-published commodities may never carry a 24h window at all.
Always guard before reading:
const c = response.data.changes?.["24h"];
if (c) console.log(`${c.percent}% over ${c.span_hours}h`);
Embedded devices should do the same — on Arduino, use an ArduinoJson filter to keep only the fields you need rather than growing the document buffer.
Windows available by plan
| Plan | Windows returned |
|---|---|
| Free, Developer, Exploration | 24h |
| Starter | 24h, 7d |
| Professional, Production | 24h, 7d, 30d |
| Scale, Reservoir, Enterprise | 24h, 7d, 30d, 90d |
Windows above your plan are omitted from the object — the response shape is otherwise unchanged.
Errors
| Code | Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key (returned as error.code) |
invalid_code | 400 | Unknown commodity code (returned under data with status: "fail") |
Examples
# Get the default commodity (BRENT_CRUDE_USD)
curl "https://api.oilpriceapi.com/v1/prices/latest" \
-H "Authorization: Token YOUR_API_KEY"
# Get a specific commodity
curl "https://api.oilpriceapi.com/v1/prices/latest?by_code=WTI_USD" \
-H "Authorization: Token YOUR_API_KEY"
# Get multiple commodities (returned as a data.prices array)
curl "https://api.oilpriceapi.com/v1/prices/latest?by_code=WTI_USD,BRENT_CRUDE_USD" \
-H "Authorization: Token YOUR_API_KEY"
# Get the latest official OPEC Reference Basket price
curl "https://api.oilpriceapi.com/v1/prices/latest?by_code=OPEC_BASKET_USD" \
-H "Authorization: Token YOUR_API_KEY"
OPEC Basket responses include source-date fields:
{
"status": "success",
"data": {
"price": 69.33,
"formatted": "$69.33",
"currency": "USD",
"code": "OPEC_BASKET_USD",
"created_at": "2026-07-02T12:00:00.000Z",
"observed_at": "2026-07-02T12:00:00.000Z",
"source_date": "2026-07-02",
"type": "spot_price",
"unit": "barrel",
"source": "opec.org"
}
}
// JavaScript
async function getLatestPrice() {
const response = await fetch(
"https://api.oilpriceapi.com/v1/prices/latest?by_code=WTI_USD",
{
headers: {
Authorization: "Token YOUR_API_KEY",
},
},
);
const data = await response.json();
// For a single code, the price object is directly in data.data
console.log(`Current price: ${data.data.formatted}`); // "$68.58"
console.log(`Numeric value: ${data.data.price}`); // 68.58
console.log(`24h change: ${data.data.changes["24h"].percent}%`);
console.log(`Source: ${data.data.metadata.source_description}`);
return data.data.price;
}
# Python
import requests
def get_latest_price(commodity_code='WTI_USD'):
url = 'https://api.oilpriceapi.com/v1/prices/latest'
headers = {'Authorization': 'Token YOUR_API_KEY'}
params = {'by_code': commodity_code}
response = requests.get(url, headers=headers, params=params)
data = response.json()
price = data['data']['price']
formatted = data['data']['formatted']
print(f"Current {commodity_code}: {formatted}")
return price
# Get multiple commodities. With several codes, prices are in data['prices']
def get_multiple_prices(codes=['WTI_USD', 'BRENT_CRUDE_USD', 'NATURAL_GAS_USD']):
url = 'https://api.oilpriceapi.com/v1/prices/latest'
headers = {'Authorization': 'Token YOUR_API_KEY'}
params = {'by_code': ','.join(codes)}
response = requests.get(url, headers=headers, params=params)
prices = response.json()['data']['prices'] # a list, not keyed by code
return {p['code']: p for p in prices}
# Ruby
require 'net/http'
require 'json'
require 'uri'
def get_latest_price(commodity_code = 'WTI_USD')
uri = URI("https://api.oilpriceapi.com/v1/prices/latest?by_code=#{commodity_code}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Token YOUR_API_KEY'
response = http.request(request)
data = JSON.parse(response.body)
price = data['data']['price']
puts "Current #{commodity_code}: $#{price}"
price
end
Rate Limits
All plans share the same request rate limit of 60 requests per rolling 60-second window per API key. Plans differ by monthly request quota, not by request rate. Bursts within the window are fine. See Rate Limiting for details.
| Plan | Requests/Month | Request Rate Limit |
|---|---|---|
| Free | 200 | 60 per rolling 60s |
| Developer | 10,000 | 60 per rolling 60s |
| Starter | 50,000 | 60 per rolling 60s |
| Professional | 100,000 | 60 per rolling 60s |
| Scale | 1,000,000 | 60 per rolling 60s |
Rate limit headers are included in responses. X-RateLimit-Limit / -Remaining / -Used track your monthly quota:
X-RateLimit-Limit: 10000
X-RateLimit-Remaining: 9876
X-RateLimit-Reset: 1785542399
Best Practices
- Cache responses: Use the response
freshnessblock and source timestamps to decide when to refetch; cadence varies by source, market hours, dataset, and plan - Request specific commodities: Use
by_codewhen possible, comma-separated for several at once - Handle rate limits: Check
X-RateLimit-*headers and implement backoff
Related Endpoints
- Historical Prices - Past price data
- Commodities List - Available commodity codes
- WebSocket Transport - Availability depends on account entitlement