Python async library for retrieving gas station data from the GasBuddy GraphQL API.
Provides three query methods covering single-station detail (prices, hours, amenities, offers), location-based station search, and a price-service query that returns prices plus regional trend data for nearby stations.
pip install py-gasbuddyimport asyncio
from py_gasbuddy import GasBuddy
async def main():
gb = GasBuddy(station_id=205033)
data = await gb.price_lookup()
print(data["name"], data["regular_gas"]["price"])
asyncio.run(main())GasBuddy(
station_id: int | None = None,
solver_url: str | None = None,
cache_file: str = "",
timeout: int = 60000,
session: aiohttp.ClientSession | None = None,
)| Parameter | Description |
|---|---|
station_id |
GasBuddy station ID — required for price_lookup() |
solver_url |
URL of a FlareSolver instance for Cloudflare bypass |
cache_file |
Path for the CSRF-token cache file (default: py_gasbuddy/gasbuddy_cache) |
timeout |
Request timeout in milliseconds (default: 60000) |
session |
Optional aiohttp.ClientSession to reuse — caller manages lifecycle |
GasBuddy's homepage is protected by Cloudflare. The library extracts a CSRF token from the HTML before making GraphQL requests. If the direct fetch fails, pass a solver_url pointing to a running FlareSolver instance:
gb = GasBuddy(station_id=205033, solver_url="http://localhost:8191/v1")Tokens are cached on disk to avoid redundant solver calls across restarts.
Returns full detail for a single station including prices, hours, amenities, brand, and offers. Requires station_id to be set at construction time.
gb = GasBuddy(station_id=205033)
data = await gb.price_lookup()
# Station metadata
print(data["name"]) # "Wawa"
print(data["phone"]) # "610-555-0100"
print(data["open_status"]) # "open"
print(data["star_rating"]) # 4.2
print(data["ratings_count"]) # 38
print(data["pay_status"]) # True (GasBuddy Pay available)
print(data["fuels"]) # ["regular_gas", "midgrade_gas", "premium_gas"]
# Address
print(data["address"]["line1"]) # "123 Main St"
print(data["address"]["locality"]) # "Phoenixville"
print(data["address"]["region"]) # "PA"
# Brands
if data["brands"]:
print(data["brands"][0]["name"]) # "Shell"
print(data["brands"][0]["imageUrl"]) # logo URL
# Hours
print(data["hours"]["status"]) # "open"
print(data["hours"]["openingHours"]) # "24 Hours"
# Prices (credit and cash)
reg = data["regular_gas"]
print(reg["price"]) # 3.27 (credit price)
print(reg["cash_price"]) # 3.17 (None if not reported)
print(reg["formatted_price"]) # "$3.27"
print(reg["last_updated"]) # "2024-09-06T09:54:05Z"
print(reg["credit"]) # reporter nickname
# Emergency status (fuel/power outage reports)
print(data["emergency_status"]["hasGas"]["reportStatus"]) # None or "has_gas"
# Loyalty offers
for offer in data["offers"]:
print(offer["id"], offer["types"], offer["use"])
# Amenities
for amenity in data["amenities"]:
print(amenity["name"]) # "ATM", "Restrooms", etc.Fuel product keys are dynamic and match whatever the station reports:
regular_gas, midgrade_gas, premium_gas, diesel, e85, e15, etc.
Returns a dict with results (list of StationSummary) and optionally next_cursor for pagination. Pass either lat+lon or zipcode.
# By ZIP code
result = await GasBuddy().location_search(zipcode=85396)
# By GPS coordinates
result = await GasBuddy().location_search(lat=33.465, lon=-112.505)
# Filter to diesel-only stations (fuel ID 4)
result = await GasBuddy().location_search(zipcode=85396, fuel=4)
# Filter to a specific brand (38 = Costco)
result = await GasBuddy().location_search(zipcode=85396, brand_id=38)
for s in result["results"]:
print(s["station_id"], s["name"], s["distance"])
print(s["address"]["locality"], s["address"]["region"])
print(s["fuels"])
# Paginate
cursor = result.get("next_cursor")
if cursor:
page2 = await GasBuddy().location_search(zipcode=85396, cursor=cursor)StationSummary fields:
| Field | Type | Description |
|---|---|---|
station_id |
str |
GasBuddy station identifier |
name |
str |
Station/brand display name |
address |
Address |
line1, line2, locality, region, postalCode, country |
brands |
list[Brand] |
Brand name, brandId, logo imageUrl |
distance |
float | None |
Distance from search point (miles) |
star_rating |
float | None |
Community star rating |
ratings_count |
int | None |
Number of ratings |
fuels |
list[str] |
Fuel products sold |
price_unit |
str | None |
"dollars_per_gallon" or "cents_per_liter" |
Returns prices for nearby stations plus regional trend data. Pass either lat+lon or zipcode. limit defaults to 5.
result = await GasBuddy().price_lookup_service(zipcode=85396, limit=10)
for station in result["results"]:
print(station["name"], station["distance"])
if "regular_gas" in station:
print(station["regular_gas"]["price"])
print(station["regular_gas"]["formatted_price"])
# Regional trends
for trend in result.get("trend", []):
print(trend["area"], trend["average_price"], trend["lowest_price"])
# Paginate
cursor = result.get("next_cursor")
if cursor:
page2 = await GasBuddy().price_lookup_service(zipcode=85396, cursor=cursor)Results have the same structure as StationPrice (see price_lookup above) minus station-only fields like amenities, hours, offers, and pay_status.
| Filter ID | Product key | Description |
|---|---|---|
| 1 | regular_gas |
Regular (85-87 Octane) |
| 2 | midgrade_gas |
Mid-Grade (89 Octane) |
| 3 | premium_gas |
Premium (91-93 Octane) |
| 4 | diesel |
Diesel |
| 5 | e85 |
E85 |
| 12 | unl88 |
Unleaded 88 (E15) |
The filter ID is the integer passed to the fuel parameter on location_search and price_lookup_service. The product key is the string used as a dictionary key in StationPrice results (e.g. data["regular_gas"]). Both are also available as FUEL_FILTER_IDS and FUEL_PRODUCTS in py_gasbuddy.consts.
All return types are TypedDict subclasses defined in py_gasbuddy.models.
class PriceNode(TypedDict):
credit: str | None # reporter nickname
price: float | None # credit price (None = not reported)
cash_price: float | None # cash price (None = not reported)
last_updated: str | None # ISO 8601 timestamp
formatted_price: str | None # "$3.27" or "131.9¢"
deal_price: float | None # price after GasBuddy Pay discount (None if no offer or no price)deal_price is computed from the pwgbDiscount field returned by the GasBuddy GraphQL API, which covers the Flash Deal and GasBuddy+ Member Savings components. The Card Savings Boost (available to GasBuddy+ members who select Fleet Card at the pump) is sourced from the mobile REST API and is not included here, so deal_price may be slightly higher than what the GasBuddy app displays for GasBuddy+ members.
class TrendData(TypedDict):
area: str # "Arizona" or "United States"
average_price: float # today's average
lowest_price: float # today's lowest (0 if not available)| Exception | Raised when |
|---|---|
MissingSearchData |
location_search or price_lookup_service called without coordinates or ZIP |
LibraryError |
Network or parse error; check logs for details |
APIError |
GasBuddy's GraphQL returned an errors field |
from py_gasbuddy.exceptions import APIError, LibraryError, MissingSearchData
try:
data = await gb.price_lookup()
except LibraryError:
print("Network or token error")
except APIError:
print("GasBuddy API error")# Clear the cached CSRF token (forces a fresh fetch on next request)
await gb.clear_cache()See the examples/ directory for runnable scripts:
| Script | Description |
|---|---|
price-lookup.py |
Full station detail by station ID |
price-lookup-service.py |
Nearby prices + trends by ZIP or GPS |
location-search.py |
Station list (no prices) by ZIP or GPS |
ev-chargers.py |
EV charger locations and status (CLI) |