OilPriceAPI Docs
GitHub
GitHub
  • Guides

    • Authentication
    • Security & Compliance
    • API Versioning
    • Data Freshness & Update Frequency
    • Data Quality and Validation
    • Coal Price Data - Complete Guide
    • Testing & Development
    • Error Codes Reference
    • SDK Code Examples
    • Webhook Signature Verification
    • Production Deployment Checklist
    • Service Level Agreement (SLA)
    • Rate Limiting & Response Headers
    • Troubleshooting Guide
    • Incident Response Guide
    • Video Tutorials

Error Codes Reference

Guide to the OilPriceAPI error responses, their meanings, and how to handle them.

Error Response Formats

OilPriceAPI does not use a single error envelope. There are three distinct shapes depending on what went wrong. Handle each explicitly.

SituationHTTP StatusBody
Missing or invalid API key401JSON with a top-level error object
Invalid commodity code400JSON with status: "fail" and an error under data
Unknown route / endpoint404Empty body (text/html), no JSON

401 — Missing or invalid API key

Both a missing key and an invalid key return the same shape with a single code, UNAUTHORIZED. There is no top-level status field and no meta block.

{
  "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",
    "docs": "https://docs.oilpriceapi.com#UNAUTHORIZED",
    "signup_url": "https://www.oilpriceapi.com/auth/signup",
    "demo_endpoint": "/v1/demo/prices"
  }
}

The error code lives at body.error.code and the request_id (useful for support) at body.error.request_id.

How to fix:

curl "https://api.oilpriceapi.com/v1/prices/latest?by_code=WTI_USD" \
  -H "Authorization: Token YOUR_API_KEY"  # Add or correct this header
  • Verify the key in your dashboard.
  • Check for typos or extra whitespace.
  • Both Token YOUR_API_KEY and Bearer YOUR_API_KEY are accepted; Token is the recommended form.

400 — Invalid commodity code

When you request a commodity code that doesn't exist, the response has a top-level status: "fail" and the error details are nested under data.

{
  "status": "fail",
  "data": {
    "error": "invalid_code",
    "message": "Code 'NOT_REAL' not found.",
    "suggestions": [],
    "invalid_codes": ["NOT_REAL"],
    "all_codes_url": "https://www.oilpriceapi.com/v1/prices/codes"
  }
}

Here the error string is at body.data.error (value "invalid_code"), and the offending codes are in body.data.invalid_codes.

How to fix:

# Use a valid commodity code. Fetch the full list from /v1/commodities.
import requests

headers = {'Authorization': 'Token YOUR_API_KEY'}
codes = requests.get(
    'https://api.oilpriceapi.com/v1/commodities',
    headers=headers,
).json()['data']

valid = {c['code'] for c in codes}
assert 'WTI_USD' in valid

The all_codes_url field in the response points at /v1/prices/codes, which is not currently a working endpoint. For the authoritative list of commodity codes, use GET /v1/commodities instead.

404 — Unknown commodity (details endpoint)

Requesting a specific commodity that doesn't exist — e.g. GET /v1/commodities/NOT_REAL — returns HTTP 404 with a JSON body whose code is COMMODITY_NOT_FOUND:

{
  "status": "fail",
  "error": {
    "code": "COMMODITY_NOT_FOUND",
    "message": "Commodity not found",
    "commodity_code": "NOT_REAL"
  }
}

Note this is a different shape from the 400 invalid_code returned by /v1/prices/latest?by_code=… — the error is under body.error (not body.data).

404 — Unknown route

Requesting a path the API doesn't recognize (a route that doesn't exist at all) returns HTTP 404 with an empty body and Content-Type: text/html. There is no JSON and no request_id, so error-handling code must not attempt to parse JSON or read a request ID from an unknown-route 404.

How to fix:

  • Check the endpoint path against the documentation.
  • Verify the API version prefix (/v1).
  • Remove trailing slashes.

HTTP Status Codes

StatusMeaningBody
200SuccessJSON payload
400Bad Request (e.g. invalid commodity code)JSON, status: "fail"
401Unauthorized — missing or invalid API keyJSON, top-level error object
404Not Found — unknown routeEmpty (text/html)
429Too Many Requests — quota exceeded—
500Internal Server Error—

Error Handling Best Practices

1. Branch on status code, not a single envelope

Because the body shape differs by status, inspect the HTTP status first and only then reach for the matching field.

import requests

def handle_response(response):
    """Return parsed data on success, or raise with a useful message."""
    if response.status_code == 200:
        return response.json()

    if response.status_code == 401:
        # Top-level `error` object. request_id is available for support.
        body = response.json()
        err = body.get('error', {})
        raise APIError(
            f"Unauthorized ({err.get('code')}): {err.get('message')} "
            f"[request_id={err.get('request_id')}]"
        )

    if response.status_code == 400:
        # `status: "fail"` with details nested under `data`.
        body = response.json()
        data = body.get('data', {})
        raise APIError(
            f"Bad request ({data.get('error')}): {data.get('message')} "
            f"invalid_codes={data.get('invalid_codes')}"
        )

    if response.status_code == 404:
        # An unknown route has an EMPTY body; the commodity-details endpoint
        # returns JSON (COMMODITY_NOT_FOUND). Parse defensively.
        try:
            body = response.json()
            err = body.get('error', {})
            raise APIError(f"Not found ({err.get('code', '404')}): {err.get('message', response.url)}")
        except ValueError:
            raise APIError('Not found (404): check the endpoint path and /v1 prefix')

    if response.status_code == 429:
        # Quota exceeded. Wait for the monthly reset or upgrade your plan.
        raise APIError('Quota exceeded (429)')

    response.raise_for_status()

2. Look up the right field per shape

There is no universal data.error.code path. Use the correct access path for each status:

StatusAccess pathExample value
401body.error.code"UNAUTHORIZED"
401body.error.request_id"fa0e389f-…"
400body.data.error"invalid_code"
400body.data.invalid_codes["NOT_REAL"]
404(no body) — check the status code only—
// JavaScript: dispatch on status, then read the right field.
async function parseError(response) {
  const status = response.status;

  if (status === 404) {
    // Empty body — nothing to parse.
    return { status, message: "Not found — check the endpoint path" };
  }

  const body = await response.json();

  if (status === 401) {
    return {
      status,
      code: body.error.code, // "UNAUTHORIZED"
      message: body.error.message,
      requestId: body.error.request_id, // present on 401 only
    };
  }

  if (status === 400) {
    return {
      status,
      code: body.data.error, // "invalid_code"
      message: body.data.message,
      invalidCodes: body.data.invalid_codes,
    };
  }

  return { status, message: `Unexpected status ${status}` };
}

3. Retry logic

Retry transient failures (network errors, 429, 5xx) with backoff. Do not retry 400 or 401 — those require a fix on your side.

async function apiRequest(url, options, maxRetries = 3) {
  let lastError;

  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);

      if (response.ok) {
        return await response.json();
      }

      // Client errors (except 429) are not retryable.
      if (
        response.status >= 400 &&
        response.status < 500 &&
        response.status !== 429
      ) {
        lastError = await parseError(response);
        throw new Error(lastError.message);
      }

      // 429 and 5xx: wait, then retry.
      const retryAfter = Number(response.headers.get("Retry-After")) || i + 1;
      await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
    } catch (err) {
      lastError = err;
      await new Promise((resolve) => setTimeout(resolve, (i + 1) * 1000));
    }
  }

  throw lastError;
}

Rate-Limit & Usage Headers

Successful (200) responses carry headers describing your monthly quota. These track the monthly request allowance, not a per-minute window.

HeaderDescription
X-RateLimit-LimitYour monthly request limit
X-RateLimit-RemainingRequests remaining this month
X-RateLimit-UsedRequests used this month
X-RateLimit-ResetEpoch (seconds) when the monthly quota resets
X-RateLimit-TierYour plan tier
X-Request-IdUnique request identifier
X-CacheCache status for the response
response = requests.get(url, headers=headers)
remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
if remaining < 100:
    logging.warning(f"Only {remaining} requests left this month")

Support

For persistent errors:

  • Email: support@oilpriceapi.com
  • Include the request_id from the response when available. Note that 401 responses carry a request_id (at body.error.request_id), but 404 responses have an empty body and therefore no request_id — in that case include the exact URL you requested instead.
Last Updated: 7/13/26, 10:12 AM
Prev
Testing & Development
Next
SDK Code Examples