An async Python SDK for interacting with the StockX API, providing both low-level endpoint mappings and high-level inventory management abstractions.
⚠️ This SDK is under active development.
⚠️ stockx.py
is in no way endorsed by or affiliated with StockX in any way. Make sure to read and understand the StockX API Terms of Service before using this package.
⚠️ This library does not circumvent API limits, expose private or proprietary data, or violate security measures. All functionality is based solely on publicly available documentation and data.
The SDK has two layers:
stockx
: low-level API client for easy access to StockX API endpointsstockx.ext
: high-level abstractions for advanced business logic
Direct mappings to StockX API endpoints through specialized interfaces:
Catalog
- Product catalog operationsListings
- Listing managementOrders
- Sales order operationsBatch
- Batch listing operations
- 🔄 Automatic token refresh and session management
- 🚦 Request throttling to prevent rate limits
- 🔁 Automatic retries with exponential backoff for failed requests
- ⚡ Response caching for invariant data (e.g., product details)
hostname
- StockX API hostnameversion
- API version to useclient_id
- OAuth client IDclient_secret
- OAuth client secretx_api_key
- API key for authenticationrefresh_token
- OAuth refresh token
The SDK converts all JSON responses to typed Python frozen dataclasses, allowing for:
- Type checking
- Easy access to data using dot notation (e.g
listing.id
) - Automatic JSON serialization and deserialization
- Pretty printing
>>> product_id = '16999d83-1c59-4913-ae9f-9a2b2ee9f1b3'
>>> product = await stockx.catalog.get_product(product_id)
>>> print(product) # Pretty print
Product:
product_id: 16999d83-1c59-4913-ae9f-9a2b2ee9f1b3
url_key: gucci-gg-supreme-monogram-apple-card-case-wallet-brown
style_id:
product_type: handbags
title: Gucci GG Supreme Monogram Apple Card Case Wallet Brown
brand: Gucci
product_attributes:
ProductAttributes:
gender: women
season: None
release_date: None
retail_price: None
colorway: None
color: Brown
>>> from datetime import datetime
>>> from stockx import OrderStatusClosed
>>> client = StockXAPIClient(...)
>>> async with StockX(client) as stockx:
... # Get an order with status DIDNOTSHIP
... async for order in stockx.orders.get_orders_history(
... from_date=datetime(2024, 1, 1),
... to_date=datetime(2024, 12, 1),
... order_status=OrderStatusClosed.DIDNOTSHIP,
... limit=1,
... page_size=50
... ):
... print(f'Order Number: {order.number} (Type: {type(order.number).__name__})')
... print(f'Status: {order.status} (Type: {type(order.status).__name__})')
... print(f'Created At: {order.created_at} (Type: {type(order.created_at).__name__})')
...
Order Number: 68322683-68222442 (Type: str)
Status: OrderStatusClosed.DIDNOTSHIP (Type: OrderStatusClosed)
Created At: 2024-10-03 16:12:40+00:00 (Type: datetime)
The SDK provides utility methods to simplify common operations.
The operation_succeeded()
method polls the status of asynchronous listing operations:
>>> # Create a new listing
>>> operation = await stockx.listings.create_listing(
... amount=100.0,
... variant_id="123",
... currency=Currency.EUR
... )
>>> # Wait for the operation to complete and check if it succeeded
>>> if await stockx.listings.operation_succeeded(operation):
... print("Listing created successfully!")
... else:
... print(f"Listing creation failed: {operation.error}")
High-level abstractions for advanced business logic and inventory management:
Inventory
- Optimized high-level interface for managing listings on StockXItem
/ListedItem
- Abstraction aggregating multiple equal listings into a single inventory entrymock_listing
- Create temporary listings for testingsearch
- Product search utilities
The Inventory
class provides a high-level interface for managing listings on StockX.
It optimizes performance by batching multiple updates together into single API calls.
Features:
- Sell or de-list items in bulk
- Set prices based on market data and custom conditions
- Update item quantities and prices on context exit
>>> client = StockXAPIClient(...)
>>> async with StockX(client) as stockx:
... async with Inventory(stockx) as inventory:
... # Get items listed for over 200 payout
... items = await (
... inventory.items()
... .filter(lambda item: item.payout() > 200)
... .all()
... )
... for item in items:
... item.price -= 20 # Reduce price by 20
... item.quantity += 1 # Increase quantity by 1
... # Changes are automatically applied when exiting context
>>> async with Inventory(stockx) as inventory:
... # Get all items with style ID 'L47450600'
... salomons_gtx = await inventory.items().filter_by(style_ids=['L47450600']).all()
...
... print(salomons_gtx[0].style_id)
... print(salomons_gtx[0].name)
... print(salomons_gtx[0].size)
... print(f'${salomons_gtx[0].payout():.2f}')
... print(salomons_gtx[0].quantity)
...
L47450600
Salomon XT-6 Gore-Tex Black Silver
11.5
$167.40
3
>>> client = StockXAPIClient(...)
>>> async with StockX(client) as stockx:
... async with Inventory(stockx) as inventory:
... # Create items from SKU and size (Air Force 1 '07 Triple White)
... af1_size_9 = await Item.from_sku_size(stockx, 'CW2288-111', 'US 9', 110.00)
... af1_size_85 = await Item.from_sku_size(stockx, 'CW2288-111', 'US 8.5', 110.00)
...
... # Create listings
... listed_items = await inventory.sell([af1_size_9, af1_size_85])
...
... # Print results
... for item in listed_items:
... print(f'Expected payout: ${item.payout()}')
...
Expected payout: $89.80
Expected payout: $89.80
>>> # Get items with specific criteria
>>> items = await (
... inventory.items()
... .filter_by(style_ids=['CW2288-111', 'L47450600'])
... .filter(lambda item: item.payout() > 150)
... .filter(lambda item: item.quantity > 5)
... .all()
... )
Using functions:
>>> # Apply 10% discount to items with payout over 200
>>> await inventory.change_price(
... items=items,
... new_price=lambda item: item.price * 0.9,
... condition=lambda item: item.payout() > 200
... )
Using async functions:
>>> async def dynamic_beat_by(item: ListedItem) -> float:
... market_data = await inventory.get_item_market_data(item)
... lowest_ask = market_data.lowest_ask.amount
... highest_bid = market_data.highest_bid.amount
... bid_ask_spread = lowest_ask - highest_bid
... return bid_ask_spread * 0.01 # Beat by 1.0% of bid-ask spread
...
>>> async def dynamic_condition(item: ListedItem) -> bool:
... market_data = await inventory.get_item_market_data(item)
... return item.price > market_data.lowest_ask.amount # Only if price > lowest ask
...
>>> await inventory.beat_lowest_ask(
... items=items,
... beat_by=dynamic_beat_by, # Dynamic amount based on market
... condition=dynamic_condition # Dynamic condition based on market
... )
The mock_listing
context manager can be used to create temporary listings for various purposes.
The Inventory
class uses it to retrieve current selling fees in order to calculate payouts accurately.
>>> async with mock_listing(stockx) as listing:
... # Check if there's a discount on selling fees
... if listing.payout.transaction_fee == 0:
... print(f'100% discount on selling fees!')
The search
module provides utilities for searching the StockX catalog.
Results are cached indefinitely to reduce the number of requests to the API.
>>> from stockx.ext import search
...
>>> product = await search.product_by_sku(stockx, 'CW2288-111')
>>> print(product.name)
Nike Air Force 1 '07 Triple White
>>> product = await search.product_by_sku(stockx, 'JABBERWOCKY')
>>> print(product)
None
Method | Endpoint | SDK Function |
---|---|---|
GET | /catalog/products/{id} |
Catalog.get_product() |
GET | /catalog/products/{id}/variants |
Catalog.get_all_product_variants() |
GET | /catalog/products/{id}/variants/{id} |
Catalog.get_product_variant() |
GET | /catalog/products/{id}/variants/{id}/market-data |
Catalog.get_variant_market_data() |
GET | /catalog/products/{id}/market-data |
Catalog.get_product_market_data() |
GET | /catalog/search |
Catalog.search_catalog() |
POST | /catalog/ingestion |
Coming soon... |
GET | /catalog/ingestion/{id} |
Coming soon... |
GET | /selling/listings/{id} |
Listings.get_listing() |
GET | /selling/listings |
Listings.get_all_listings() |
POST | /selling/listings |
Listings.create_listing() |
DELETE | /selling/listings/{id} |
Listings.delete_listing() |
PUT | /selling/listings/{id}/activate |
Listings.activate_listing() |
PUT | /selling/listings/{id}/deactivate |
Listings.deactivate_listing() |
PATCH | /selling/listings/{id} |
Listings.update_listing() |
GET | /selling/listings/{id}/operations/{id} |
Listings.get_listing_operation() |
GET | /selling/listings/{id}/operations |
Listings.get_all_listing_operations() |
POST | /selling/batch/create-listing |
Batch.create_listings() |
GET | /selling/batch/create-listing/{id} |
Batch.create_listings_status() |
GET | /selling/batch/create-listing/{id}/items |
Batch.create_listings_items() |
POST | /selling/batch/update-listing |
Batch.update_listings() |
GET | /selling/batch/update-listing/{id} |
Batch.update_listings_status() |
GET | /selling/batch/update-listing/{id}/items |
Batch.update_listings_items() |
POST | /selling/batch/delete-listing |
Batch.delete_listings() |
GET | /selling/batch/delete-listing/{id} |
Batch.delete_listings_status() |
GET | /selling/batch/delete-listing/{id}/items |
Batch.delete_listings_items() |
GET | /selling/orders/{id} |
Orders.get_order() |
GET | /selling/orders/history |
Orders.get_orders_history() |
GET | /selling/orders/active |
Orders.get_active_orders() |
GET | /selling/orders/{orderNumber}/shipping-document/{shippingId} |
Coming soon... |