Historical Prices
Status: ✅ Available | 📈 Time-Series | 🔄 Multiple Intervals
Access historical price data for oil and commodity markets with customizable time ranges and intervals.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/prices/historical | Custom date range or relative window; daily buckets by default |
| GET | /v1/prices/past_day | Raw spot price points for the past 24 hours, or one calendar day with at= |
| GET | /v1/prices/past_week | Raw spot price points for the past 7 days |
| GET | /v1/prices/past_month | Raw spot price points for the past 30 days |
| GET | /v1/prices/past_year | Raw spot price points for the past 365 days |
/v1/prices/historical is the canonical endpoint for full archives. It requires by_code, defaults to interval=daily, and supports up to 500 rows per page. The fixed-window endpoints default to raw collected points and cap raw pages at 100 rows. Publication frequency varies by commodity; a raw point is not necessarily a real-time market tick.
Authentication
See Authentication Guide for API key setup.
Custom Historical Parameters
These parameters apply to GET /v1/prices/historical:
| Parameter | Type | Required | Description | Default | Example |
|---|---|---|---|---|---|
by_code | string | Yes | One code or a comma-separated list | — | WTI_USD, JET_FUEL_USD |
by_type | string | No | Stored price type | all | spot_price |
start_date | date | No | Inclusive UTC start date | plan limit | 2025-01-01 |
end_date | date | No | Inclusive UTC end date | now | 2025-12-31 |
past | string | No | Relative window when explicit dates are omitted | — | 30d, 6m, 1y |
interval | string | No | Aggregation interval (see Interval Parameter for alias spellings) | daily | raw, hourly, daily, weekly, monthly |
page | integer | No | Page number | 1 | 1, 2, 3 |
per_page | integer | No | Rows per page; values above 500 are clamped | 500 | 100, 500 |
Fixed-window parameters
These apply to past_day, past_week, past_month and past_year:
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
by_code | string | Yes | One code or a comma-separated list | WTI_USD |
interval | string | No | Aggregation interval (see below); default raw | 1d, 1w |
at | date | No | past_day only. Return the points for one UTC calendar day instead of the trailing 24 hours. YYYY-MM-DD; an unparseable value returns 400 "Invalid at format". /v1/prices/YYYY-MM-DD is an alias that serves this lookup directly (no redirect), with the date landing in at | at=2026-08-27 |
as_of | string | No | Point-in-time view: rows as they stood at that instant, with later source revisions rolled back. ISO 8601 date or datetime, not in the future, and requires interval=raw (aggregates cannot be vintage-corrected row by row). Revisions are tracked from 2026-07-28; a vintage earlier than that returns the rows as first collected | as_of=2026-08-01T00:00:00Z |
A vintage request echoes its parameters in two headers:
| Header | Value |
|---|---|
X-Vintage-As-Of | The applied instant, UTC ISO 8601 (a bare date becomes its end of day) |
X-Vintage-Revision-Coverage-Since | 2026-07-28 — no revisions before this date are recorded |
Historical depth is entitlement-aware even when you omit all date bounds. Use GET /v1/commodities/{code} to see your_access, source cadence, coverage, and direct links to the canonical OpenAPI operation and this guide.
Interval Parameter
The interval parameter controls time-based aggregation for historical data. When specified, prices are averaged within each time bucket, reducing data transfer and improving performance for charting and dashboard applications.
| Interval | Description | Aggregation | Use Case |
|---|---|---|---|
raw | No aggregation (default on fixed-window endpoints) | Individual price points | Full granularity, detailed analysis |
hourly | Hourly aggregation | Average price per hour | Intraday analysis, short-term trends |
daily | Daily aggregation | Average price per day | Daily charts, week-over-week comparison |
weekly | Weekly aggregation | Average price per week | Monthly/quarterly charts, medium-term trends |
monthly | Monthly aggregation | Average price per month | Yearly charts, long-term trends |
Every historical endpoint accepts the same alias spellings per family, case-insensitively: 1h/h/hour/1hour for hourly; 1d/d/day/1day for daily; 1w/w/week/1week for weekly; 1m/m/month/1month for monthly. An unknown spelling on /v1/prices/historical returns 400 listing valid_intervals. Some endpoints support fewer families (futures history has no hourly bars); the error names what that endpoint accepts.
When an interval is supplied, each returned point has type set to the bucket average (e.g. daily_average), source set to aggregated, and an added price_type field.
Benefits:
- Reduced Data Transfer: Fewer data points for the same time range (e.g., 30 days ≈ 30 daily averages vs. several thousand raw ~5–10 minute points)
- Lower Rate Limit Usage: Aggregated responses use the same rate limit as raw data
- Optimized for Charting: Pre-aggregated data is ideal for time-series visualizations
- Consistent Formatting: All prices rounded to 2 decimal places for currency consistency
Response
Success (200) - Raw Data
data.prices is an array of raw spot points, roughly 5–10 minutes apart:
{
"status": "success",
"data": {
"prices": [
{
"price": 67.84,
"formatted": "$67.84",
"currency": "USD",
"code": "WTI_USD",
"created_at": "2026-07-02T13:54:43.160Z",
"updated_at": "2026-07-02T13:54:43.160Z",
"type": "spot_price",
"unit": "barrel",
"source": "market_reporting"
},
{
"price": 67.79,
"formatted": "$67.79",
"currency": "USD",
"code": "WTI_USD",
"created_at": "2026-07-02T13:46:11.402Z",
"updated_at": "2026-07-02T13:46:11.402Z",
"type": "spot_price",
"unit": "barrel",
"source": "market_reporting"
}
]
}
}
Success (200) - With Interval Aggregation
When using the interval parameter, points are bucket averages — type becomes the average type, source is aggregated, and a price_type field is added:
{
"status": "success",
"data": {
"prices": [
{
"price": 67.94,
"formatted": "$67.94",
"currency": "USD",
"code": "WTI_USD",
"created_at": "2026-07-02T00:00:00.000Z",
"type": "daily_average",
"price_type": "daily_average",
"source": "aggregated"
},
{
"price": 68.41,
"formatted": "$68.41",
"currency": "USD",
"code": "WTI_USD",
"created_at": "2026-07-01T00:00:00.000Z",
"type": "daily_average",
"price_type": "daily_average",
"source": "aggregated"
}
]
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
price | number | Price value (rounded to 2 decimal places) |
formatted | string | Currency-formatted price with symbol |
currency | string | Currency code (USD, EUR, GBP) |
code | string | Commodity code |
created_at | string | ISO 8601 timestamp (UTC) |
observed_at | string | ISO 8601 source observation timestamp when available; useful for daily official-source records |
source_date | string | Date-only source reference date when available, such as OPEC Basket daily values |
updated_at | string | ISO 8601 timestamp of the last update (UTC) — raw points only |
unit | string | Unit of measure (e.g. barrel) — raw points only |
type | string | Price type: spot_price for raw, or the bucket type (e.g. daily_average) for aggregated |
price_type | string | Added only on aggregated points; mirrors the bucket average type |
source | string | Data source: aggregated for interval data, or a source label (e.g. eia, market_reporting) for raw data |
Alongside data.prices, a response may carry a data.metadata block:
| Field | Type | Description |
|---|---|---|
metadata.instrument_disclosures | array | Present when a requested code is a calendar-month average swap (SINGAPORE_JET_KEROSENE_USD, SINGAPORE_MOGAS_92_USD): one disclosure per instrument, not per row. Same shape as instrument_disclosure on Latest Prices. Do not average these series into a monthly figure |
metadata.quarantine | object | Present when verified-unreliable historical values were withheld from the response — see below |
Withheld values (metadata.quarantine)
A small number of dates carry values that were verified to be wrong at the source or at ingestion (for example an intraday scaling error contaminating a daily average). Those dates are excluded from every historical response rather than served; the stored rows are not deleted. When the exclusion touched your window the response says so:
{
"status": "success",
"data": {
"prices": ["…"],
"metadata": {
"quarantine": {
"applied": true,
"excluded_dates_count": 1,
"codes": ["BRENT_CRUDE_USD"],
"entries": [
{ "code": "BRENT_CRUDE_USD", "date": "2021-03-11", "reason": "intraday scaling outlier contaminated the published daily aggregate" }
],
"entries_truncated": false,
"message": "Verified unreliable historical values were withheld from this response; no source rows were deleted.",
"recovery": "…"
}
}
}
}
entries[] lists the withheld code/date/reason triples; entries_truncated is true when the list was capped. The block is absent when nothing in your window was withheld.
Pagination
Historical endpoints return paginated results for performance. /v1/prices/historical defaults to and caps pages at 500 rows; fixed-window raw endpoints cap pages at 100 rows. Page through larger ranges with page=N. The total-count header is X-Total (not X-Total-Count).
| Header | Description | Example |
|---|---|---|
X-Total | Total number of records | 2016 |
X-Total-Pages | Total number of pages | 21 |
X-Page | Current page number | 1 |
X-Per-Page | Applied records per page | 500 |
X-Vintage-As-Of | Only with as_of=: the applied instant | 2026-08-01T23:59:59Z |
X-Vintage-Revision-Coverage-Since | Only with as_of=: first date with revision tracking | 2026-07-28 |
This endpoint does not send a Link header. Page through results with X-Total-Pages and page=N.
OPEC Reference Basket
Use OPEC_BASKET_USD for the official OPEC Reference Basket price. The series is sourced from OPEC's official daily XML archive, uses source: "opec.org", and carries source_date for the OPEC reference date.
curl "https://api.oilpriceapi.com/v1/prices/historical?by_code=OPEC_BASKET_USD&start_date=2003-01-01&interval=daily" \
-H "Authorization: Token YOUR_API_KEY"
OPEC Basket historical data is available on paid plans through the normal historical endpoints. Plan historical-depth limits still apply.
Examples
Basic Usage
# Get past 24 hours of raw WTI data (all individual price points)
curl "https://api.oilpriceapi.com/v1/prices/past_day?by_code=WTI_USD" \
-H "Authorization: Token YOUR_API_KEY"
# Get past week with pagination
curl "https://api.oilpriceapi.com/v1/prices/past_week?by_code=WTI_USD&page=1&per_page=50" \
-H "Authorization: Token YOUR_API_KEY"
# Get CSV format
curl "https://api.oilpriceapi.com/v1/prices/past_year?by_code=WTI_USD&format=csv" \
-H "Authorization: Token YOUR_API_KEY"
Using Interval Aggregation
# Get past month as daily averages (~30 points instead of thousands of raw points)
curl "https://api.oilpriceapi.com/v1/prices/past_month?by_code=BRENT_CRUDE_USD&interval=1d" \
-H "Authorization: Token YOUR_API_KEY"
# Get past year as weekly averages for long-term trending
curl "https://api.oilpriceapi.com/v1/prices/past_year?by_code=WTI_USD&interval=1w" \
-H "Authorization: Token YOUR_API_KEY"
# Get past week as hourly averages for intraday analysis
curl "https://api.oilpriceapi.com/v1/prices/past_week?by_code=NATURAL_GAS_USD&interval=1h" \
-H "Authorization: Token YOUR_API_KEY"
# Get past year as monthly averages for annual trends
curl "https://api.oilpriceapi.com/v1/prices/past_year?by_code=BRENT_CRUDE_USD&interval=1m" \
-H "Authorization: Token YOUR_API_KEY"
JavaScript/TypeScript
// Get past week with daily aggregation
async function getWeeklyPrices() {
const response = await fetch(
"https://api.oilpriceapi.com/v1/prices/past_week?by_code=WTI_USD&interval=1d",
{
headers: {
Authorization: "Token YOUR_API_KEY",
},
},
);
const data = await response.json();
return data.data.prices; // Returns ~7 daily averages
}
// Get optimized data for charting (monthly averages for past year)
async function getChartData(commodityCode = "WTI_USD") {
const response = await fetch(
`https://api.oilpriceapi.com/v1/prices/past_year?by_code=${commodityCode}&interval=1m`,
{
headers: { Authorization: "Token YOUR_API_KEY" },
},
);
const data = await response.json();
// Format for chart libraries (e.g., Chart.js, Recharts)
return data.data.prices.map((price) => ({
date: new Date(price.created_at),
value: price.price,
formatted: price.formatted,
}));
}
// Handle pagination for raw (non-aggregated) data
async function getAllRawData(
endpoint = "past_week",
commodityCode = "WTI_USD",
) {
let allData = [];
let page = 1;
let totalPages = 1;
while (page <= totalPages) {
const response = await fetch(
`https://api.oilpriceapi.com/v1/prices/${endpoint}?by_code=${commodityCode}&page=${page}`,
{
headers: { Authorization: "Token YOUR_API_KEY" },
},
);
totalPages = parseInt(response.headers.get("X-Total-Pages"));
const data = await response.json();
if (data.data?.prices) {
allData.push(...data.data.prices);
}
page++;
}
return allData;
}
Python
import requests
from datetime import datetime
# Get past month as daily averages
def get_monthly_averages(commodity_code='WTI_USD'):
url = 'https://api.oilpriceapi.com/v1/prices/past_month'
headers = {'Authorization': 'Token YOUR_API_KEY'}
params = {
'by_code': commodity_code,
'interval': '1d' # Daily aggregation
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
return data['data']['prices']
# Get past year with weekly aggregation for trending
def get_yearly_trend(commodity_code='BRENT_CRUDE_USD'):
url = 'https://api.oilpriceapi.com/v1/prices/past_year'
headers = {'Authorization': 'Token YOUR_API_KEY'}
params = {
'by_code': commodity_code,
'interval': '1w' # Weekly aggregation
}
response = requests.get(url, headers=headers, params=params)
data = response.json()
# Format for data analysis
prices = []
for price in data['data']['prices']:
prices.append({
'date': datetime.fromisoformat(price['created_at'].replace('Z', '+00:00')),
'price': price['price'],
'type': price['type']
})
return prices
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") |
| — | 400 | as_of rejected: "Invalid as_of: … Use ISO8601 — as_of=2026-06-02 or as_of=2026-06-02T00:00:00Z.", "as_of must not be in the future.", or "as_of requires interval=raw. Aggregated intervals cannot be vintage-corrected row-by-row." (under data.error) |
| — | 400 | at rejected on past_day: "Invalid at format" (under data.error) |
Rate Limits
All plans share the same request rate limit of 60 requests per rolling 60-second window per API key. Plans differ by request quota (daily on Free, monthly on paid plans), not by a per-endpoint rate ladder. Bursts within the window are fine. See Rate Limiting for details.
| Plan | Request Quota | Request Rate Limit |
|---|---|---|
| Free | 50 / day | 60 per rolling 60s |
| Free Trial (7 days) | 10,000 | 60 per rolling 60s |
| Developer | 10,000 / month | 60 per rolling 60s |
| Starter | 50,000 / month | 60 per rolling 60s |
| Professional | 100,000 / month | 60 per rolling 60s |
| Scale | 1,000,000 / month | 60 per rolling 60s |
Current tier limits are machine-readable at GET https://api.oilpriceapi.com/v1/meta/limits (no key required); this table is rendered from it at build time. Read limits from the endpoint rather than hardcoding them.
Related Endpoints
- Latest Prices - Current market prices
- Commodities List - Available commodity codes
- WebSocket Streaming - Real-time updates