Python client library for Best Buy's Catalog and Commerce APIs
Project description
python-bestbuy
A Python client library for Best Buy's APIs, providing synchronous and asynchronous HTTP clients for interacting with Best Buy's Catalog and Commerce services.
Features
- Dual API Support: Full support for both the Catalog API (products, categories, stores, recommendations, open box) and Commerce API (orders, fulfillment, pricing)
- Sync and Async Clients: Both synchronous and asynchronous clients for each API
- Query Builder: Pythonic DSL for constructing search queries with operator overloading
- Streaming: Cursor-mark based auto-pagination for efficient iteration over large result sets
- Automatic Pagination: Built-in paginators for page-level and item-level iteration
- Type Safety: Full Pydantic model validation for requests and responses
- Rate Limiting: Built-in request throttling (default 5 requests/second)
- Retry Logic: Configurable automatic retry on server errors and transient failures
- Session Management: Automatic session handling for Commerce API authentication
- Payment Encryption: Built-in utilities for encrypting credit card data for guest orders
- Sandbox Support: Easy switching between production and sandbox environments
Installation
pip install python-bestbuy
Or using uv:
uv add python-bestbuy
Requirements
- Python 3.10+
- httpx
- pydantic-xml
- cryptography
Usage
Catalog API
The Catalog API provides access to Best Buy's product catalog, categories, store locations, recommendations, and open box inventory.
from bestbuy.clients.catalog import CatalogClient, AsyncCatalogClient
# Initialize the client
client = CatalogClient(api_key="your-api-key")
Query Builder
The query builder provides a Pythonic way to construct Best Buy's search filter syntax. It's optional -- you can always pass raw query strings instead.
from bestbuy.query import where, search, area
# Simple comparisons
q = where("manufacturer") == "apple" # "manufacturer=apple"
q = where("salePrice") < 1000 # "salePrice<1000"
q = where("manufacturer") != "apple" # "manufacturer!=apple"
# Boolean values (auto-lowercased)
q = where("onSale") == True # "onSale=true"
# AND (&) and OR (|) operators
q = (where("manufacturer") == "apple") & (where("salePrice") < 1000)
q = (where("onSale") == True) | (where("freeShipping") == True)
# IN operator for set membership
q = where("sku").is_in([43900, 2088495, 7150065]) # "sku in(43900,2088495,7150065)"
# Wildcard / existence check
q = where("driveCapacityGb").exists() # "driveCapacityGb=*"
# Keyword search
q = search("laptop") # "search=laptop"
q = search("oven", "stainless", "steel") # "search=oven&search=stainless&search=steel"
# Geographic area query (stores)
q = area("55423", 10) # "area(55423,10)"
q = area(lat=44.97, lng=-93.26, distance=5) # "area(44.97,-93.26,5)"
# Complex grouping with automatic parenthesization
q = (where("platform") == "psp") & (
(where("salePrice") <= 15) | (
(where("salePrice") <= 20) & (where("inStorePickup") == True)
)
)
# "platform=psp&(salePrice<=15|(salePrice<=20&inStorePickup=true))"
# Use with any search method (pass as string)
results = client.products.search(query=str(q), page_size=10)
# Raw query strings still work
results = client.products.search(query="manufacturer=apple&salePrice<1000")
Products
# Get a single product by SKU
product = client.products.get(sku=6487435)
print(f"{product.name}: ${product.sale_price}")
# Get specific attributes only
product = client.products.get(sku=6487435, show=["name", "salePrice", "manufacturer"])
# Search for products
results = client.products.search(
query="manufacturer=apple&salePrice<1000",
show=["sku", "name", "salePrice"],
sort="salePrice",
sort_order="asc",
page=1,
page_size=10
)
for product in results.products:
print(f"{product.sku}: {product.name}")
# List all products
results = client.products.list(page=1, page_size=10)
# Get warranties for a product (returns typed Warranty objects)
warranties = client.products.get_warranties(sku=6487435)
for w in warranties:
print(f"{w.short_name}: ${w.current_price} ({w.term})")
# Check real-time in-store availability
availability = client.products.get_real_time_availability(
sku=6487435, postal_code="55401"
)
print(f"Pickup eligible: {availability.ispu_eligible}")
for store in availability.stores:
print(f" {store.name}: low stock = {store.low_stock}")
# Check availability across multiple stores
response = client.products.availability(sku=6487435, store_ids=[281, 1358])
for product in response.products:
for store in product.stores:
print(f" Store {store.store_id}: {store.name}")
# Paginate through search results
for page in client.products.search_pages(query="onSale=true", page_size=100):
for product in page.products:
print(product.name)
# Iterate over individual products across all pages
for product in client.products.search_pages(query="onSale=true").items():
print(product.name)
# Limit pagination to a maximum number of pages
for product in client.products.search_pages(query="onSale=true", max_pages=5).items():
print(product.name)
# Stream products using cursor-mark pagination (most efficient for large result sets)
for product in client.products.stream(query="onSale=true", page_size=100):
print(product.name)
Categories
# Get a single category
category = client.categories.get(category_id="abcat0100000")
print(f"{category.name}: {category.id}")
# Search for categories
results = client.categories.search(query="name=Laptops*")
for category in results.categories:
print(category.name)
# List all categories
results = client.categories.list(page=1, page_size=10)
# Paginate through categories
for category in client.categories.search_pages().items():
print(category.name)
# Stream all categories
for category in client.categories.stream():
print(category.name)
Stores
# Get a single store by ID
store = client.stores.get(store_id=281)
print(f"{store.name}: {store.city}, {store.region}")
# Search for stores
results = client.stores.search(query="city=Minneapolis")
for store in results.stores:
print(f"{store.store_id}: {store.name}")
# List all stores
results = client.stores.list(page=1, page_size=10)
# Find stores near a ZIP code
results = client.stores.search_by_area(postal_code="55401", distance=25)
for store in results.stores:
print(f"{store.name} - {store.distance} miles")
# Find stores near coordinates
results = client.stores.search_by_area(lat=44.9778, lng=-93.2650, distance=10)
# Paginate through stores by area
for store in client.stores.search_by_area_pages(postal_code="55401").items():
print(store.name)
# Stream all stores
for store in client.stores.stream():
print(store.name)
Open Box
# List all open box products
results = client.openbox.list(page_size=10)
for item in results.results:
print(f"{item.names.title}: {len(item.offers)} offers")
# Get open box offers for a specific product
results = client.openbox.get(sku=6487435)
# Search open box products by category
results = client.openbox.search("categoryId=abcat0502000")
# Stream all open box products
for item in client.openbox.stream():
print(f"{item.sku}: {item.offers}")
Recommendations
# Get trending products
trending = client.recommendations.trending()
for product in trending.results:
print(f"Trending: {product.names.title}")
# Get trending products in a specific category
trending = client.recommendations.trending(category_id="abcat0502000")
# Get most viewed products
most_viewed = client.recommendations.most_viewed()
for product in most_viewed.results:
print(f"Most viewed: {product.names.title}")
# Get products also viewed with a specific product
also_viewed = client.recommendations.also_viewed(sku=6487435)
for product in also_viewed.results:
print(f"Also viewed: {product.names.title}")
# Get products also bought with a specific product
also_bought = client.recommendations.also_bought(sku=6487435)
# Get products ultimately bought after viewing a product
ultimately_bought = client.recommendations.viewed_ultimately_bought(sku=6487435)
Version
# Get API and package version
version_info = client.version()
print(f"API version: {version_info.api_version}")
print(f"Package version: {version_info.package_version}")
Async Catalog Client
import asyncio
from bestbuy.clients.catalog import AsyncCatalogClient
async def main():
client = AsyncCatalogClient(api_key="your-api-key")
# All operations are async
product = await client.products.get(sku=6487435)
print(product.name)
# Async pagination
async for product in client.products.search_pages(query="onSale=true").items():
print(product.name)
# Async streaming
async for product in client.products.stream(query="onSale=true"):
print(product.name)
asyncio.run(main())
Commerce API
The Commerce API provides access to Best Buy's order management, fulfillment options, pricing, and payment services.
from bestbuy.clients.commerce import CommerceClient, AsyncCommerceClient
# Initialize the client (sandbox mode by default)
client = CommerceClient(
api_key="your-api-key",
username="your-username", # For registered orders
password="your-password",
sandbox=True # Use sandbox environment
)
# Production mode
client = CommerceClient(
api_key="your-api-key",
sandbox=False
)
Authentication
Authentication is required for registered orders (orders charged to your company credit card).
# Using context manager (recommended)
with client.auth:
# Session is automatically managed
response = client.orders.submit_registered(order)
# Session is automatically logged out
# With explicit credentials
with client.auth(username="user", password="pass"):
response = client.orders.submit_registered(order)
# Manual login/logout
client.auth.login()
try:
response = client.orders.submit_registered(order)
finally:
client.auth.logout()
Fulfillment Operations
# Check product availability
availability = client.fulfillment.check_availability(sku="5628900")
print(f"Shipping available: {availability.available_for_shipping}")
print(f"Pickup available: {availability.available_for_pickup}")
print(f"Delivery available: {availability.available_for_delivery}")
print(f"Max quantity: {availability.max_quantity}")
# Get shipping options
shipping = client.fulfillment.get_shipping_options(
sku="5628900",
address1="123 Main St",
city="Minneapolis",
state="MN",
postalcode="55401"
)
for option in shipping.options:
print(f"{option.name}: ${option.price} - Delivery by {option.expected_delivery_date}")
# Find stores with product availability
stores = client.fulfillment.find_stores(
sku="5628900",
zip_code="55401",
store_count=5
)
for store in stores.stores:
print(f"{store.name}: {store.availability_msg}")
# Get home delivery options (for large items)
delivery_options = client.fulfillment.get_delivery_options(
sku="5628900",
zip_code="55401"
)
for option in delivery_options.options:
print(f"{option.delivery_date}: {option.start_time} - {option.end_time}")
# Get delivery services (installation, haul away, etc.)
services = client.fulfillment.get_delivery_services(
sku="5628900",
zip_code="55401"
)
for service in services.delivery_services:
print(f"{service.service_display_name}: ${service.price}")
Pricing Operations
# Get unit price for a SKU
price = client.pricing.get_unit_price(sku="5628900")
print(f"Price: ${price.unit_price.value}")
# Get combined product service info (availability, price, shipping, stores)
product_info = client.pricing.get_product_service(
sku="5628900",
zip_code="55401"
)
Order Operations
from bestbuy.models.commerce import (
OrderSubmitRegisteredRequest,
OrderSubmitGuestRequest,
OrderList,
OrderItem,
Fulfillment,
AddressFulfillment,
ShippingAddress,
Tender,
)
# Create a registered order (requires authentication)
order = OrderSubmitRegisteredRequest(
id="my-order-123",
order_list=OrderList(
id="list-1",
items=[
OrderItem(
id="item-1",
quantity=1,
sku="5628900"
)
]
),
fulfillment=Fulfillment(
address_fulfillment=AddressFulfillment(
address=ShippingAddress(
address1="123 Main St",
city="Minneapolis",
state="MN",
postalcode="55401"
),
shipping_option_key="1" # From get_shipping_options
)
),
tender=Tender()
)
# Review order (get accurate tax calculation)
with client.auth:
review_response = client.orders.review(order)
print(f"Total: ${review_response.total}")
# Submit the order
submit_response = client.orders.submit_registered(order)
print(f"Order ID: {submit_response.id_map}")
# Query an existing order (no auth required)
order_details = client.orders.query(
order_id="BBY01-123456789",
last_name="Smith",
phone_number="6125551234"
)
print(f"Status: {order_details.status}")
# Lookup order by ID (requires auth)
with client.auth:
order_details = client.orders.lookup(order_id="BBY01-123456789")
Guest Orders
Guest orders are charged to the customer's credit card and require payment encryption.
from bestbuy.utils.encryption import create_encrypted_payment_token
# Get encryption key
encryption_key = client.encryption.get_encryption_key()
# Create encrypted payment token
payment_token = create_encrypted_payment_token(
card_number="5424180279791773",
base64_encoded_public_key=encryption_key.base64_encoded_public_key_bytes,
terminal_id=encryption_key.terminal_id,
track_id=encryption_key.track_id,
key_id=encryption_key.key_id
)
# Create guest order with encrypted payment
guest_order = OrderSubmitGuestRequest(
id="guest-order-123",
order_list=OrderList(
id="list-1",
items=[
OrderItem(id="item-1", quantity=1, sku="5628900")
]
),
fulfillment=Fulfillment(
address_fulfillment=AddressFulfillment(
address=ShippingAddress(
address1="123 Main St",
city="Minneapolis",
state="MN",
postalcode="55401"
),
shipping_option_key="1"
)
),
tender=Tender(
# Include encrypted payment token in tender
)
)
# Submit guest order (no auth required)
response = client.orders.submit_guest(guest_order)
Encryption Operations
# Get the public encryption key for guest orders
key_response = client.encryption.get_encryption_key()
print(f"Terminal ID: {key_response.terminal_id}")
print(f"Track ID: {key_response.track_id}")
print(f"Key ID: {key_response.key_id}")
Async Commerce Client
import asyncio
from bestbuy.clients.commerce import AsyncCommerceClient
async def main():
client = AsyncCommerceClient(
api_key="your-api-key",
username="your-username",
password="your-password"
)
# Check availability
availability = await client.fulfillment.check_availability(sku="5628900")
print(availability.available_for_shipping)
# Async authentication context
async with client.auth:
response = await client.orders.submit_registered(order)
asyncio.run(main())
Configuration Options
Catalog Client Options
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
str | Required | Best Buy API key |
base_url |
str | https://api.bestbuy.com |
API base URL |
timeout_ms |
int | 60000 |
Request timeout in milliseconds |
requests_per_second |
int | 5 |
Rate limit (requests per second, 0 to disable) |
max_retries |
int | 0 |
Max retry attempts on 5xx/transport errors |
retry_interval_ms |
int | 2000 |
Delay between retries in milliseconds |
headers |
dict | None | Custom HTTP headers merged into every request |
log_level |
int | logging.WARNING |
Logging level |
Commerce Client Options
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
str | Required | Best Buy API key |
username |
str | None | Billing account username |
password |
str | None | Billing account password |
partner_id |
str | None | Partner ID for orders |
sandbox |
bool | True |
Use sandbox environment |
auto_logout |
bool | True |
Auto-logout on session end |
base_url |
str | Auto | API base URL (auto-set based on sandbox) |
timeout_ms |
int | 60000 |
Request timeout in milliseconds |
requests_per_second |
int | 5 |
Rate limit (requests per second, 0 to disable) |
max_retries |
int | 0 |
Max retry attempts on 5xx/transport errors |
retry_interval_ms |
int | 2000 |
Delay between retries in milliseconds |
headers |
dict | None | Custom HTTP headers merged into every request |
log_level |
int | logging.WARNING |
Logging level |
Error Handling
from bestbuy.exceptions import APIError, ConfigError, SessionRequiredError
try:
product = client.products.get(sku=999999999)
except APIError as e:
print(f"API Error: {e.message}")
print(f"Error Code: {e.code}")
print(f"Response: {e.response_text}")
except ConfigError as e:
print(f"Configuration Error: {e}")
# Commerce API specific
try:
client.orders.submit_registered(order) # Without authentication
except SessionRequiredError as e:
print("Must be logged in to submit orders")
Sandbox Testing
For the Commerce API, use the sandbox environment for testing:
client = CommerceClient(
api_key="your-sandbox-api-key",
sandbox=True # Default
)
# Test credit card for sandbox
# Card Number: 5424180279791773
# Expiration: 12/2025
# CVV: 999
Development
Running Tests
uv run pytest
Running Linter
uv run ruff check
uv run ruff format --check
Running Type Checks
uv run mypy
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file python_bestbuy-0.2.1.tar.gz.
File metadata
- Download URL: python_bestbuy-0.2.1.tar.gz
- Upload date:
- Size: 41.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
63d829b94334dbb2c01ca63f1ad2c7efb6441215601b91232c73b86afdef110b
|
|
| MD5 |
48111eadfeb2a0604f9afc39110cd89e
|
|
| BLAKE2b-256 |
bb040441d7e1308fdb0bb7a8c1df31243cd35560967dfd91ef25656053f271f4
|
Provenance
The following attestation bundles were made for python_bestbuy-0.2.1.tar.gz:
Publisher:
ci.yml on bbify/python-bestbuy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_bestbuy-0.2.1.tar.gz -
Subject digest:
63d829b94334dbb2c01ca63f1ad2c7efb6441215601b91232c73b86afdef110b - Sigstore transparency entry: 1281527260
- Sigstore integration time:
-
Permalink:
bbify/python-bestbuy@7b51a8deecd3ac208fbd6e9d59fec8dbc79e516b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/bbify
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@7b51a8deecd3ac208fbd6e9d59fec8dbc79e516b -
Trigger Event:
release
-
Statement type:
File details
Details for the file python_bestbuy-0.2.1-py3-none-any.whl.
File metadata
- Download URL: python_bestbuy-0.2.1-py3-none-any.whl
- Upload date:
- Size: 47.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eaae284c55169ec143784db2c1007dc5761d2773ced673f5742efc9fb721a342
|
|
| MD5 |
e0a787521483b95fe7928809dbf74cd0
|
|
| BLAKE2b-256 |
df50c713a084dfca45133227591bbc6d35ca9a1a8461f8dac34d96248d62e808
|
Provenance
The following attestation bundles were made for python_bestbuy-0.2.1-py3-none-any.whl:
Publisher:
ci.yml on bbify/python-bestbuy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
python_bestbuy-0.2.1-py3-none-any.whl -
Subject digest:
eaae284c55169ec143784db2c1007dc5761d2773ced673f5742efc9fb721a342 - Sigstore transparency entry: 1281527376
- Sigstore integration time:
-
Permalink:
bbify/python-bestbuy@7b51a8deecd3ac208fbd6e9d59fec8dbc79e516b -
Branch / Tag:
refs/tags/v0.2.1 - Owner: https://github.com/bbify
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@7b51a8deecd3ac208fbd6e9d59fec8dbc79e516b -
Trigger Event:
release
-
Statement type: