Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable changes to the OilPriceAPI Python SDK will be documented in this fil

## [Unreleased]

### Added

- **Typed LTL and parcel fuel-surcharge clients (#101).** `client.fuel_surcharge`
on both `OilPriceAPI` and `AsyncOilPriceAPI` covers all six
`/v1/fuel-surcharge` routes: `list()`, `latest(carrier)`,
`history(carrier, page=, per_page=)`, `parcel_list()`,
`parcel_latest(carrier)`, `parcel_latest_rate(carrier, service_level)` and
`parcel_history(carrier, service_level, page=, per_page=)`. Responses are
`FuelSurchargeRate`, `FuelSurchargeHistoryPage` (with the server's
`meta`) and `ParcelFuelSurchargeCarrier` models typed from production
payloads captured on 2026-09-13: `effective_date` is a `date`,
`retrieved_at` a timezone-aware `datetime`, and `source`, nullable
`doe_diesel_price` and `diesel_band` are kept as sent. A success body
missing a field the API always sends raises
`OilPriceAPIError(code="MALFORMED_RESPONSE")` instead of defaulting it.
Carrier slugs, service levels and pagination are validated before any
request; out-of-range `page`/`per_page` are refused because the API clamps
them silently.
- **Fuel-surcharge 400/404 bodies populate `error.suggestions`.** The
`covered_carriers` and `available_service_levels` lists the API returns with
an unknown carrier or a missing service level are now surfaced the same way
commodity suggestions are.

## [1.15.0] - 2026-09-13

### Fixed
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,33 @@ An empty permit search or production history is a valid data state. Do not
infer broader well-level coverage from the presence of permit data or an SDK
helper; dataset and account availability come from the current API response.

## Carrier Fuel Surcharges

Weekly fuel surcharges for LTL carriers and, per service level, for parcel
carriers. Each rate keeps the carrier's `effective_date` and the `source` URL
and `retrieved_at` time it was retrieved from; a null the API sends (for
example `doe_diesel_price` on parcel rates) stays `None`.

```python
import os

from oilpriceapi import OilPriceAPI

with OilPriceAPI(api_key=os.environ["OILPRICEAPI_KEY"]) as client:
odfl = client.fuel_surcharge.latest("odfl")
history = client.fuel_surcharge.history("odfl", per_page=10)
ups_ground = client.fuel_surcharge.parcel_latest_rate("ups", "ground")

print(odfl.surcharge_percent, odfl.effective_date, odfl.source)
print(history.meta.total_count, [row.effective_date for row in history.history])
print(ups_ground.surcharge_percent, ups_ground.service_level)
```

An unknown or uncovered carrier raises `DataNotFoundError` with the covered
carriers in `error.suggestions`. `page` must be 1 or more and `per_page` 1 to
100; the SDK refuses other values rather than letting the API clamp them.
See [`examples/fuel_surcharge.py`](examples/fuel_surcharge.py).

## Complete pandas DataFrames

Install the optional pandas support, then request a historical DataFrame:
Expand Down
4 changes: 4 additions & 0 deletions docs/reference/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@

::: oilpriceapi.resources.drilling.DrillingIntelligenceResource

## Fuel Surcharges

::: oilpriceapi.resources.fuel_surcharge.FuelSurchargeResource

## Well Production (Beta)

::: oilpriceapi.resources.well_production.WellProductionResource
Expand Down
52 changes: 52 additions & 0 deletions examples/fuel_surcharge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Carrier fuel surcharges: LTL and parcel (#101).

Usage:
OILPRICEAPI_KEY=... python examples/fuel_surcharge.py

Prints the latest LTL surcharge per carrier, one carrier's recent weekly
history, and the latest parcel surcharge per service level. Every row shows the
carrier's effective date and where the value was retrieved from.
"""

import os

from oilpriceapi import OilPriceAPI
from oilpriceapi.exceptions import DataNotFoundError


def main() -> None:
with OilPriceAPI(api_key=os.environ["OILPRICEAPI_KEY"]) as client:
print("LTL carriers")
rates = client.fuel_surcharge.list()
for rate in rates:
print(
f" {rate.carrier:<22} {rate.surcharge_percent:>6.2f}% "
f"effective {rate.effective_date} retrieved {rate.retrieved_at:%Y-%m-%d}"
)

if rates:
carrier = rates[0].carrier
page = client.fuel_surcharge.history(carrier, per_page=4)
print(f"\n{carrier} history ({page.meta.total_count} weeks on record)")
for row in page.history:
diesel = "n/a" if row.doe_diesel_price is None else f"${row.doe_diesel_price:.3f}"
print(f" {row.effective_date} {row.surcharge_percent:.2f}% DOE diesel {diesel}")
print(f" source: {page.history[0].source}" if page.history else " no rows")

print("\nParcel carriers")
for parcel in client.fuel_surcharge.parcel_list():
for rate in parcel.service_levels:
print(
f" {parcel.carrier:<6} {rate.service_level:<26} "
f"{rate.surcharge_percent:>6.2f}% effective {rate.effective_date}"
)

try:
client.fuel_surcharge.latest("fedex-freight")
except DataNotFoundError as error:
print(f"\nNot covered: {error.message}")
print(f"Covered carriers: {', '.join(error.suggestions)}")


if __name__ == "__main__":
main()
10 changes: 10 additions & 0 deletions oilpriceapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@
DieselPrice,
DieselStation,
DieselStationsResponse,
FuelSurchargeDieselBand,
FuelSurchargeHistoryMeta,
FuelSurchargeHistoryPage,
FuelSurchargeRate,
MarketBrief,
MarketBriefCommodity,
MarketBriefForecast,
ParcelFuelSurchargeCarrier,
PriceAlert,
Subscription,
SubscriptionEvent,
Expand Down Expand Up @@ -76,6 +81,11 @@
"MarketBrief",
"MarketBriefCommodity",
"MarketBriefForecast",
"FuelSurchargeRate",
"FuelSurchargeDieselBand",
"FuelSurchargeHistoryMeta",
"FuelSurchargeHistoryPage",
"ParcelFuelSurchargeCarrier",
"Subscription",
"SubscriptionEvent",
"SubscriptionEventsPage",
Expand Down
Loading