OilPriceAPI guides

Waha gas price API: calculate Henry Hub basis in Python

By OilPriceAPI · API contract and public quotes checked November 5, 2026

Waha is a Permian spot-gas benchmark. Henry Hub is a different gas market. Both API codes quote USD per MMBtu, so their difference expresses Waha basis against Henry Hub. A negative Waha price is valid data; the value should not be clamped to zero. The tricky part is matching actual source dates, not the arithmetic.

Request the two source series

MarketCodeBasis role
Waha Hub spotNATURAL_GAS_WAHALocal price
Henry HubNATURAL_GAS_USDReference price

Fetch each with /v1/prices/latest?by_code=…. In the example, Waha basis is Waha minus Henry Hub; the sign tells you which market is above the other. See the Waha gas price API guide for the benchmark's scope.

Calculate only from comparable observations

Set OILPRICEAPI_KEY, save the example as waha_basis.pyand run python3 waha_basis.py. It accepts finite negative prices, but requires each response to identify its code, USD/MMBtu unit, current status and as_of source date. Different dates or missing source details produce an unavailable result instead of a manufactured spread.

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

codes = ("NATURAL_GAS_WAHA", "NATURAL_GAS_USD")
key = os.environ.get("OILPRICEAPI_KEY")
if not key:
    raise SystemExit("Set OILPRICEAPI_KEY before running this example")

def observation(code):
    url = "https://api.oilpriceapi.com/v1/prices/latest?" + urlencode({"by_code": code})
    request = Request(url, headers={"Authorization": "Token " + key})
    try:
        with urlopen(request, timeout=10) as response:
            payload = json.load(response)
    except HTTPError as error:
        if error.code in (401, 403):
            raise SystemExit("Check the API key and access to both gas series") from error
        if error.code == 402:
            raise SystemExit("Check your plan's access to both gas series") from error
        if error.code == 429:
            raise SystemExit("Request limit reached; wait before retrying") from error
        raise SystemExit(f"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 payload.get("status") != "success" or not isinstance(payload.get("data"), dict):
        return None
    row = payload["data"]
    value = row.get("price")
    if (row.get("code") != code or not isinstance(value, (int, float))
            or isinstance(value, bool) or not math.isfinite(value)
            or row.get("currency") != "USD"
            or str(row.get("unit", "")).lower() != "mmbtu"
            or row.get("stale") or row.get("data_status") != "current"
            or row.get("synthetic") or not row.get("as_of")):
        return None
    try:
        source_day = datetime.fromisoformat(row["as_of"].replace("Z", "+00:00")).date()
    except (TypeError, ValueError):
        return None
    return value, source_day

waha = observation(codes[0])
henry = observation(codes[1])
if waha is None or henry is None:
    print("Basis unavailable: check each code, source date and freshness")
elif waha[1] != henry[1]:
    print("Basis unavailable: source dates differ", waha[1], henry[1])
else:
    print("Waha minus Henry Hub:", round(waha[0] - henry[0], 3),
          "USD/MMBtu on", waha[1])

On November 5, the public Waha proxy exposed a November 3 source date, while the public Henry Hub proxy omitted as_of. That public response is not enough to calculate a source-matched basis; the example deliberately stops in that state. The authenticated latest-prices reference describes source and freshness fields. Your application should set its own acceptable observation window before acting on a derived basis.

Explore the data

Check both market observations, then try the API with your own key.