OilPriceAPI Docs
GitHub
GitHub
  • Getting Started

    • Quick Start Guide - OilPriceAPI
    • Making Requests
    • Handling Responses
  • SDK Quick Start

    • Python SDK
    • JavaScript/Node.js SDK

Handling Responses

Learn how to parse and work with API responses from OilPriceAPI.

Response Format

All API responses follow a consistent JSON structure:

Success Response

A single-commodity request returns a flat object in data. A multi-commodity or historical request returns data.prices (an array) plus a data.metadata block:

{
  "status": "success",
  "data": {
    "prices": [
      {
        "code": "WTI_USD",
        "price": 68.58,
        "formatted": "$68.58",
        "currency": "USD",
        "created_at": "2026-07-03T13:43:01.099Z"
      }
    ],
    "metadata": {
      "request_id": "6390b34903a8d3f0",
      "timestamp": "2026-07-03T13:47:37Z",
      "version": "v1"
    }
  }
}

Error Response

Errors do not share a single envelope — the shape depends on the status code (see Error Codes for the full reference):

// 401 Unauthorized — missing or invalid API key
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Missing or invalid API key. Include header: Authorization: Token YOUR_API_KEY",
    "status": 401,
    "request_id": "fa0e389f-ca86-47b2-b167-2b4645a2fb8b"
  }
}

// 400 Bad Request — unknown commodity code
{
  "status": "fail",
  "data": {
    "error": "invalid_code",
    "message": "Code 'NOT_REAL' not found.",
    "invalid_codes": ["NOT_REAL"]
  }
}

A 404 Not Found returns an empty body (no JSON), so check the HTTP status before parsing.

Response Status Codes

Status CodeDescription
200 OKRequest successful
400 Bad RequestInvalid parameters or request format
401 UnauthorizedInvalid or missing API key
404 Not FoundResource not found
429 Too Many RequestsRate limit exceeded
500 Internal Server ErrorServer error (rare)

Parsing Price Data

Single Commodity Response

// Response from /v1/prices/latest?by_code=WTI_USD
{
  "status": "success",
  "data": {
    "price": 74.52,
    "formatted": "$74.52",
    "currency": "USD",
    "code": "WTI_USD",
    "created_at": "2025-12-29T15:30:00.000Z",
    "type": "spot_price",
    "source": "market_reporting",
    "metadata": {
      "source": "market_reporting",
      "source_description": "Business Insider - Most accurate source matching ICE exchange prices"
    }
  }
}

// Parsing in JavaScript
const response = await fetch(url, options);
const result = await response.json();

if (result.status === 'success') {
  const { price, formatted, code, created_at } = result.data;
  console.log(`${code}: ${formatted}`);  // "WTI_USD: $74.52"
  console.log(`Updated: ${new Date(created_at).toLocaleString()}`);
}

Multiple Commodities Response

When requesting multiple commodities, data.prices is an array of price objects (one per commodity):

# Response from /v1/prices/latest?by_code=WTI_USD,BRENT_CRUDE_USD
response = {
  "status": "success",
  "data": {
    "prices": [
      {
        "price": 68.58,
        "formatted": "$68.58",
        "currency": "USD",
        "code": "WTI_USD",
        "created_at": "2026-07-03T13:43:01.099Z",
        "type": "spot_price"
      },
      {
        "price": 71.98,
        "formatted": "$71.98",
        "currency": "USD",
        "code": "BRENT_CRUDE_USD",
        "created_at": "2026-07-03T13:43:01.099Z",
        "type": "spot_price"
      }
    ],
    "metadata": {
      "request_id": "6390b34903a8d3f0",
      "timestamp": "2026-07-03T13:47:37Z",
      "version": "v1"
    }
  }
}

# Parsing in Python
import requests

response = requests.get(url, headers=headers)
data = response.json()

if data['status'] == 'success':
    # data['data']['prices'] is a list - iterate or key it by code
    for price_data in data['data']['prices']:
        print(f"{price_data['code']}: {price_data['formatted']}")

Working with Historical Data

Historical Response Structure

Historical endpoints return the price points as an array under data.prices:

{
  "status": "success",
  "data": {
    "prices": [
      {
        "price": 67.84,
        "formatted": "$67.84",
        "currency": "USD",
        "code": "WTI_USD",
        "created_at": "2026-07-02T13:54:43.160Z",
        "type": "spot_price"
      },
      {
        "price": 67.61,
        "formatted": "$67.61",
        "currency": "USD",
        "code": "WTI_USD",
        "created_at": "2026-07-02T13:44:41.010Z",
        "type": "spot_price"
      },
      {
        "price": 67.42,
        "formatted": "$67.42",
        "currency": "USD",
        "code": "WTI_USD",
        "created_at": "2026-07-02T13:34:38.920Z",
        "type": "spot_price"
      }
    ]
  }
}

Processing Historical Data

// JavaScript - Calculate average price
function calculateAverage(historicalData) {
  const prices = historicalData.data.prices; // data.prices is an array
  const sum = prices.reduce((acc, item) => acc + item.price, 0);
  return sum / prices.length;
}

// Find price trends
function analyzeTrend(historicalData) {
  const prices = historicalData.data.prices;
  const firstPrice = prices[prices.length - 1].price; // oldest
  const lastPrice = prices[0].price; // most recent
  const change = lastPrice - firstPrice;
  const changePercent = (change / firstPrice) * 100;

  return {
    trend: change > 0 ? "upward" : "downward",
    changeAmount: change.toFixed(2),
    changePercent: changePercent.toFixed(2),
  };
}
# Python - Create DataFrame for analysis
import pandas as pd
import requests

def process_historical_data(response_data):
    """Convert API response to pandas DataFrame"""
    prices = response_data['data']['prices']  # data.prices is an array
    df = pd.DataFrame(prices)
    df['created_at'] = pd.to_datetime(df['created_at'])
    df.set_index('created_at', inplace=True)

    # Calculate moving averages
    df['ma_5'] = df['price'].rolling(window=5).mean()
    df['ma_10'] = df['price'].rolling(window=10).mean()

    return df

# Usage
response = requests.get(url, headers=headers)
data = response.json()
df = process_historical_data(data)
print(df.describe())

Error Handling

Handling Errors

Because the error shape depends on the status code, branch on the HTTP status rather than a single error.code field. A 404 has no body, so guard the JSON parse:

async function makeAPICall(url, options) {
  try {
    const response = await fetch(url, options);

    if (response.status === 401) {
      // { "error": { "code": "UNAUTHORIZED", "message": ..., "request_id": ... } }
      const body = await response.json();
      console.error(`Auth failed (${body.error.code}):`, body.error.message);
      console.error("Include header: Authorization: Token YOUR_API_KEY");
      return null;
    }

    if (response.status === 400) {
      // { "status": "fail", "data": { "error": "invalid_code", "invalid_codes": [...] } }
      const body = await response.json();
      console.error("Bad request:", body.data.error, body.data.message);
      // For a bad commodity code, fetch the valid list from GET /v1/commodities
      return null;
    }

    if (response.status === 429) {
      const retryAfter = response.headers.get("Retry-After") || 60;
      console.warn(`Rate limited. Retry after ${retryAfter}s.`);
      return null;
    }

    if (response.status === 404) {
      // 404 returns an empty body — do not parse JSON
      console.error("Not found:", url);
      return null;
    }

    return await response.json();
  } catch (err) {
    console.error("Network error:", err);
    return null;
  }
}

Retry Logic

import time
from typing import Optional, Dict, Any

class APIClient:
    def __init__(self, api_key: str, max_retries: int = 3):
        self.api_key = api_key
        self.max_retries = max_retries

    def make_request(self, endpoint: str, params: Dict[str, Any]) -> Optional[Dict]:
        """Make API request with automatic retry on failure"""

        for attempt in range(self.max_retries):
            try:
                response = requests.get(
                    f"https://api.oilpriceapi.com/v1{endpoint}",
                    params=params,
                    headers={'Authorization': f'Token {self.api_key}'},
                    timeout=10
                )

                if response.status_code == 429:
                    # Rate limited - wait before retry
                    retry_after = int(response.headers.get('Retry-After', 60))
                    if attempt < self.max_retries - 1:
                        time.sleep(retry_after)
                        continue

                response.raise_for_status()
                return response.json()

            except requests.exceptions.RequestException as e:
                if attempt == self.max_retries - 1:
                    print(f"Failed after {self.max_retries} attempts: {e}")
                    return None

                # Exponential backoff
                wait_time = 2 ** attempt
                time.sleep(wait_time)

        return None

Response Headers

Important headers included in responses:

X-RateLimit-Limit: 10000           # Monthly request quota for your plan
X-RateLimit-Remaining: 9876        # Requests remaining this month
X-RateLimit-Used: 124              # Requests used this month
X-RateLimit-Reset: 1785542399      # Unix timestamp when the monthly quota resets
X-RateLimit-Tier: developer        # Your current plan tier
X-Request-Id: 8c1fc2b1-b8e2-46ae-bd18-b6916d526e7a  # Request ID — include it in support tickets

These headers track your monthly quota, not a per-minute window. Provide the X-Request-Id when contacting support.

Monitoring Usage

class UsageMonitor {
  constructor() {
    this.usage = {
      limit: null,
      remaining: null,
      resetAt: null,
    };
  }

  updateFromHeaders(headers) {
    this.usage.limit = parseInt(headers.get("x-ratelimit-limit"));
    this.usage.remaining = parseInt(headers.get("x-ratelimit-remaining"));
    this.usage.resetAt = new Date(
      parseInt(headers.get("x-ratelimit-reset")) * 1000,
    );

    this.checkUsage();
  }

  checkUsage() {
    const percentUsed =
      ((this.usage.limit - this.usage.remaining) / this.usage.limit) * 100;

    if (percentUsed > 90) {
      console.warn(`API usage at ${percentUsed.toFixed(1)}%`);
      this.sendUsageAlert();
    }
  }

  getDaysUntilReset() {
    const now = new Date();
    const msUntilReset = this.usage.resetAt - now;
    return Math.ceil(msUntilReset / (1000 * 60 * 60 * 24));
  }
}

Data Transformation

Format for Display

function formatPriceDisplay(priceData) {
  const formatter = new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: priceData.currency,
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  });

  const pct = priceData.changes?.["24h"]?.percent ?? 0;
  return {
    formatted: priceData.formatted, // API returns a pre-formatted string, e.g. "$68.58"
    unit: priceData.unit,
    change: pct > 0 ? "↑" : "↓",
    changeColor: pct > 0 ? "green" : "red",
    changeText: `${pct > 0 ? "+" : ""}${pct}%`,
  };
}

// Usage — find the commodity in the prices array
const wti = result.data.prices.find((p) => p.code === "WTI_USD");
const display = formatPriceDisplay(wti);
console.log(`${display.formatted}/${display.unit} ${display.changeText}`);
// Output: $68.58/barrel +1.06%

Export to CSV

import csv
from datetime import datetime

def export_to_csv(historical_data, filename):
    """Export historical price data to CSV"""

    prices = historical_data['data']['prices']

    with open(filename, 'w', newline='') as csvfile:
        fieldnames = ['created_at', 'code', 'price', 'currency', 'source']
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for point in prices:
            writer.writerow({
                'created_at': point['created_at'],
                'code': point['code'],
                'price': point['price'],
                'currency': point['currency'],
                'source': point.get('source', ''),
            })

    print(f"Exported {len(prices)} records to {filename}")

OilPriceAPI returns spot price points (a single price per timestamp), not OHLCV candles. For period averages, request an aggregated interval (e.g. interval=1d).

WebSocket Responses (Professional and above)

Real-time streaming is available on the Professional plan and above. Each message carries a code and price consistent with the REST fields:

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  if (message.type === "price_update") {
    updatePriceDisplay(message.data); // { code, price, currency, created_at, ... }
  }
};

See the WebSocket documentation for the authoritative message schema, authentication, and subscription model.

Next Steps

  • Explore the API Reference for detailed endpoint documentation
  • Learn about Marine Fuels specialty endpoints
  • Set up WebSocket connections for real-time updates (Professional and above)
Last Updated: 7/16/26, 3:41 PM
Prev
Making Requests