Skip to content

bbify/python-bestbuy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

python-bestbuy

CI

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

About

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.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages