OilPriceAPI guides

Crack spread API in Python: 3-2-1 and European gasoil

By OilPriceAPI · API docs checked November 5, 2026

A crack spread compares a crude input with refined-product outputs. The 3-2-1 model represents three barrels of crude yielding two barrels of gasoline and one barrel of distillate. The European gasoil crack is a different benchmark: ICE Low Sulphur Gasoil against ICE Brent. Use the API's named calculation instead of subtracting quotes that have different physical units.

Two endpoints, two response shapes

CalculationPathRead from
WTI 3-2-1/v1/spreads/crack?code=WTI_321_CRACKA row in the data array; check period
European gasoil/v1/spreads/gasoil-crackThe data object; check timestamp and both legs

Gasoil is quoted in USD per tonne and Brent in USD per barrel. The European endpoint uses the exchange convention of 7.45 barrels per tonne before subtracting Brent. Each futures leg names its own contract month; they can differ around a roll. The gasoil crack reference explains the formula and source bounds.

Run the Python example

Set OILPRICEAPI_KEY, save this as crack_spreads.pyand run python3 crack_spreads.py. Calculated metrics are available during an active trial and on Developer or higher. A Free-plan key returns 403; if a source leg has no recent price, the gasoil endpoint returns 404. The example gives a next action for each instead of turning it into a zero spread.

import json
import math
import os
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen

base = "https://api.oilpriceapi.com"
key = os.environ.get("OILPRICEAPI_KEY")
if not key:
    raise SystemExit("Set OILPRICEAPI_KEY before running this example")

def get(path):
    request = Request(base + path, headers={"Authorization": "Token " + key})
    try:
        with urlopen(request, timeout=10) as response:
            payload = json.load(response)
    except HTTPError as error:
        if error.code == 401:
            raise SystemExit("Check OILPRICEAPI_KEY; this request was not authorized")
        if error.code == 402:
            raise SystemExit("Check your plan and remaining request allowance")
        if error.code == 403:
            raise SystemExit("Calculated spreads need an active trial or paid plan")
        if error.code == 404:
            print(path, "has no recent source data; try again after the market updates")
            return None
        if error.code == 429:
            raise SystemExit("Rate limited; wait before retrying")
        raise SystemExit(f"Spread API request failed with HTTP {error.code}") from error
    except URLError as error:
        raise SystemExit("API connection failed; retry after checking the network") from error
    if not isinstance(payload, dict) or payload.get("status") != "success":
        raise RuntimeError("Spread API did not return a usable result")
    return payload.get("data")

cracks = get("/v1/spreads/crack?code=WTI_321_CRACK")
crack_row = None
if isinstance(cracks, list):
    for row in cracks:
        if not isinstance(row, dict) or row.get("code") != "WTI_321_CRACK":
            continue
        value = row.get("value")
        if (isinstance(value, (int, float)) and not isinstance(value, bool)
                and math.isfinite(value) and row.get("period")
                and row.get("unit") and row.get("currency")):
            crack_row = row
            break
if crack_row is None:
    print("WTI 3-2-1 unavailable: check source data and response fields")
else:
    print("WTI 3-2-1", crack_row["value"], crack_row["currency"],
          "per", crack_row["unit"], "for", crack_row["period"])

gasoil = get("/v1/spreads/gasoil-crack")
if isinstance(gasoil, dict):
    value = gasoil.get("value")
    legs = gasoil.get("components") or {}
    product = legs.get("product") if isinstance(legs, dict) else None
    crude = legs.get("crude") if isinstance(legs, dict) else None
    if (isinstance(value, (int, float)) and not isinstance(value, bool)
            and math.isfinite(value) and gasoil.get("timestamp")
            and gasoil.get("unit") and isinstance(product, dict)
            and isinstance(crude, dict) and product.get("code")
            and crude.get("code") and product.get("contract_month")
            and crude.get("contract_month")):
        print("European gasoil crack", value, gasoil["unit"],
              "as of", gasoil["timestamp"])
        print("product", product["code"], product["contract_month"])
        print("crude", crude["code"], crude["contract_month"])
    else:
        print("European gasoil crack unavailable: inspect both source legs")
else:
    print("European gasoil crack unavailable: inspect source response")

Check each response's date before charting it, and keep the gasoil leg contract months beside the calculated value. The spreads API reference covers access, historical coverage and data rights. Public display or redistribution of derived analytics requires review.

Explore the spread

See the current market context, then choose the API access that fits your use.