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
| Market | Code | Basis role |
|---|---|---|
| Waha Hub spot | NATURAL_GAS_WAHA | Local price |
| Henry Hub | NATURAL_GAS_USD | Reference 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.