Idiomatic Python SDK for Sitecore OrderCloud
Project description
ordercloud-python
A fully typed, async-first Python SDK for Sitecore OrderCloud.
Complete API coverage — all 632 operations across 60 resources, generated from the official OpenAPI spec. Built for modern Python:
- Async and sync clients —
async with OrderCloudClient(...)orwith SyncOrderCloudClient(...). Same API shape, your choice of runtime. - Pydantic v2 models — every API resource is a typed, validated model. snake_case fields, PascalCase aliases for API compatibility.
- Typed extended properties —
Product[MyXpModel]gives you type-safe access to OrderCloud'sxpfields. - Auto-pagination —
async for product in paginate(client.products.list)handles page iteration automatically. - Retry with backoff — configurable retries on 429/5xx with exponential backoff and
Retry-Aftersupport. - Middleware hooks — intercept requests and responses for logging, metrics, or header injection.
- Structured logging — standard Python
loggingmodule, DEBUG/WARNING levels. - Full type annotations —
py.typedmarker for downstream type checking with mypy, pyright, etc. - 784 tests, 97% coverage — 759 unit tests (mocked HTTP) + 25 integration tests (live sandbox).
Installation
pip install ordercloud-python
Requires Python 3.10+.
Quick Start
Async (default)
import asyncio
from ordercloud import OrderCloudClient
async def main():
async with OrderCloudClient.create(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
) as client:
# List products
products = await client.products.list(page_size=10)
for p in products.items:
print(f"{p.id}: {p.name}")
# Create a product
from ordercloud.models import Product
product = await client.products.create(Product(
name="My Product",
active=True,
))
print(f"Created: {product.id}")
asyncio.run(main())
Sync
from ordercloud import SyncOrderCloudClient
with SyncOrderCloudClient.create(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
) as client:
products = client.products.list(page_size=10)
for p in products.items:
print(f"{p.id}: {p.name}")
The sync client wraps the async client internally — same features, same API shape, no await.
Configuration
| Parameter | Default | Description |
|---|---|---|
client_id |
(required) | OAuth2 client ID |
client_secret |
"" |
OAuth2 client secret (empty for public clients) |
base_url |
https://api.ordercloud.io/v1 |
API base URL |
auth_url |
https://auth.ordercloud.io/oauth/token |
OAuth2 token endpoint |
scopes |
["FullAccess"] |
OAuth2 scopes to request |
timeout |
30.0 |
HTTP request timeout (seconds) |
max_retries |
0 |
Max retries on 429/5xx (0 = disabled) |
retry_backoff |
0.5 |
Base delay in seconds for exponential backoff |
Regional Environments
| Environment | API Base URL | Auth URL |
|---|---|---|
| US Production | https://api.ordercloud.io/v1 |
https://auth.ordercloud.io/oauth/token |
| US Sandbox | https://sandboxapi.ordercloud.io/v1 |
https://sandboxauth.ordercloud.io/oauth/token |
| Europe West Production | https://westeurope-production.ordercloud.io/v1 |
https://westeurope-production-auth.ordercloud.io/oauth/token |
| Europe West Sandbox | https://westeurope-sandbox.ordercloud.io/v1 |
https://westeurope-sandbox-auth.ordercloud.io/oauth/token |
| Australia East Production | https://australiaeast-production.ordercloud.io/v1 |
https://australiaeast-production-auth.ordercloud.io/oauth/token |
| Japan East Production | https://japaneast-production.ordercloud.io/v1 |
https://japaneast-production-auth.ordercloud.io/oauth/token |
Typed Extended Properties (xp)
OrderCloud models support extended properties (xp) — arbitrary JSON attached to any resource. By default, xp is dict[str, Any]. You can type it with a Pydantic model:
from pydantic import BaseModel
from ordercloud.models import Product
class MyProductXp(BaseModel):
color: str
weight_kg: float
# Create with typed xp
product = Product[MyProductXp](
name="Widget",
xp=MyProductXp(color="red", weight_kg=1.5),
)
product.xp.color # str, not Any
# Deserialise with typed xp (API responses use PascalCase — the SDK handles both)
data = {"Name": "Widget", "xp": {"color": "blue", "weight_kg": 2.0}}
product = Product[MyProductXp].model_validate(data)
product.xp.color # "blue"
Unparameterized usage (Product(xp={"anything": True})) still works — fully backward compatible. PascalCase field names are accepted as aliases for construction and deserialization (e.g. Product(Name="Widget") still works), but snake_case is the canonical Python form.
Auto-Pagination
Iterate through all pages automatically:
from ordercloud import paginate
# Async
async for product in paginate(client.products.list, search="widget"):
print(product.name)
# Works with positional args too
async for order in paginate(client.orders.list, OrderDirection.Incoming):
print(order.id)
For the sync client:
from ordercloud import paginate_sync
for product in paginate_sync(client.products.list, search="widget"):
print(product.name)
Retry Logic
Enable automatic retries on transient failures (429 rate limit, 5xx server errors):
client = OrderCloudClient.create(
client_id="...",
client_secret="...",
max_retries=3, # Retry up to 3 times
retry_backoff=0.5, # 0.5s, 1s, 2s exponential backoff
)
Respects Retry-After headers. Never retries on 4xx client errors (400, 401, 403, 404, etc.).
Structured Logging
The SDK logs via Python's standard logging module under the ordercloud logger:
import logging
logging.basicConfig(level=logging.DEBUG)
# Or configure just the SDK logger
logging.getLogger("ordercloud").setLevel(logging.DEBUG)
| Level | What's logged |
|---|---|
DEBUG |
Every request (Request: GET /products) and response (Response: GET /products 200) |
WARNING |
Retry attempts with status code and backoff delay |
Middleware Hooks
Register hooks to intercept requests and responses:
from ordercloud import RequestContext, ResponseContext
async def add_correlation_id(ctx: RequestContext) -> None:
ctx.headers["X-Correlation-ID"] = generate_id()
async def log_timing(ctx: ResponseContext) -> None:
print(f"{ctx.request.method} {ctx.request.path} -> {ctx.response.status_code}")
client.add_before_request(add_correlation_id)
client.add_after_response(log_timing)
Before-request hooks receive a mutable RequestContext — modify headers, params, or json before the request is sent. After-response hooks receive a ResponseContext with the request details and response. Hooks are called on every attempt, including retries.
API Coverage
The SDK covers all 60 resources and 632 operations in the OrderCloud API. Models and resource clients are generated from the official OpenAPI v3 spec (version 1.0.445).
Core Commerce
| Resource | Operations | Highlights |
|---|---|---|
| Products | 18 | CRUD, variants, specs, suppliers, assignments |
| Orders | 29 | CRUD, submit, approve, decline, cancel, complete, forward, split, ship, promotions |
| Line Items | 9 | CRUD, shipping address management, cross-order listing |
| Cart | 37 | Full shopping cart lifecycle, checkout, payments, promotions |
| Bundles | 12 | CRUD, product/catalog assignments |
| Catalogs | 15 | CRUD, product/bundle/category assignments |
| Categories | 15 | CRUD, hierarchical with depth control, assignments |
Buyers & Users
| Resource | Operations | Highlights |
|---|---|---|
| Buyers | 7 | CRUD, seller relationships |
| Buyer Groups | 6 | CRUD |
| Users | 11 | CRUD, access tokens, move, cross-buyer listing |
| User Groups | 9 | CRUD, user assignments |
| Me | 80 | Full buyer-perspective API (addresses, orders, products, subscriptions, etc.) |
| Admin Users | 8 | CRUD, token revocation, account unlock |
| Admin User Groups | 9 | CRUD, user assignments |
Pricing & Promotions
| Resource | Operations | Highlights |
|---|---|---|
| Price Schedules | 8 | CRUD, price breaks |
| Promotions | 9 | CRUD, assignments |
| Discounts | 9 | CRUD, assignments |
| Specs | 15 | CRUD, options, product assignments |
Fulfillment
| Resource | Operations | Highlights |
|---|---|---|
| Shipments | 12 | CRUD, items, ship-from/ship-to addresses |
| Payments | 7 | CRUD, transactions |
| Order Returns | 14 | CRUD, submit, approve, decline, complete, cancel |
Organisation & Security
| Resource | Operations | Highlights |
|---|---|---|
| Suppliers | 9 | CRUD, buyer relationships |
| Security Profiles | 9 | CRUD, assignments |
| API Clients | 15 | CRUD, secrets, assignments |
| Addresses | 9 | CRUD, assignments |
| Cost Centers | 9 | CRUD, assignments |
| Credit Cards | 9 | CRUD, assignments |
| Spending Accounts | 9 | CRUD, assignments |
Integrations & Infrastructure
| Resource | Operations | Highlights |
|---|---|---|
| Webhooks | 6 | CRUD |
| Integration Events | 10 | CRUD, calculate, estimate shipping |
| Message Senders | 11 | CRUD, assignments, CC listeners |
| Subscriptions | 6 | CRUD |
| Entity Syncs | 40 | Full sync infrastructure |
| Delivery Configurations | 6 | CRUD |
| Inventory Records | 18 | CRUD, variant records, assignments |
Usage Examples
Products
# List with search and pagination
products = await client.products.list(
search="widget",
search_on="Name,Description",
sort_by="Name",
page=1,
page_size=20,
)
print(f"Found {products.meta.total_count} products")
# Get by ID
product = await client.products.get("my-product-id")
# Create
from ordercloud.models import Product
product = await client.products.create(Product(
id="my-product",
name="Widget",
description="A fine widget",
active=True,
))
# Update (PUT — full replace)
product = await client.products.save("my-product", Product(
name="Updated Widget",
active=True,
))
# Patch (partial update)
product = await client.products.patch("my-product", {"Description": "An even finer widget"})
# Delete
await client.products.delete("my-product")
Orders
from ordercloud.models import Order, OrderDirection
# List incoming orders
orders = await client.orders.list(OrderDirection.Incoming, page_size=50)
# Create an outgoing order
order = await client.orders.create(
OrderDirection.Outgoing,
Order(comments="Rush delivery"),
)
# Order workflow
order = await client.orders.submit(OrderDirection.Outgoing, order.id)
order = await client.orders.approve(OrderDirection.Incoming, order.id)
order = await client.orders.complete(OrderDirection.Incoming, order.id)
Line Items
from ordercloud.models import LineItem, OrderDirection
# Add a line item to an order
line_item = await client.line_items.create(
OrderDirection.Outgoing, "order-id",
LineItem(product_id="my-product", quantity=3),
)
# List line items on an order
line_items = await client.line_items.list(OrderDirection.Outgoing, "order-id")
for li in line_items.items:
print(f" {li.product_id} x{li.quantity}")
Catalogs and Categories
from ordercloud.models import Catalog, Category
# Create a catalog
catalog = await client.catalogs.create(Catalog(
name="Spring Collection",
active=True,
))
# Create a category within it
category = await client.categories.create(catalog.id, Category(
name="New Arrivals",
active=True,
))
# List categories (with depth control)
categories = await client.categories.list(catalog.id, depth="all")
Filtering
All list() methods accept a filters dict for server-side filtering:
# Products with Active=true and Name starting with "Widget"
products = await client.products.list(filters={
"Active": True,
"Name": "Widget*",
})
# Orders with Total > 100
orders = await client.orders.list(filters={"Total": ">100"})
Error Handling
from ordercloud import OrderCloudError, AuthenticationError
try:
product = await client.products.get("nonexistent")
except AuthenticationError as e:
# 401 or 403
print(f"Auth failed: {e}")
except OrderCloudError as e:
# Any other API error (4xx/5xx)
print(f"API error {e.status_code}: {e}")
for error in e.errors:
print(f" {error.error_code}: {error.message}")
Code Generation
Models and resource clients are generated from the OrderCloud OpenAPI v3 spec using the included codegen tool:
pip install -e ".[codegen]"
python -m tools.codegen --spec path/to/ordercloud-openapi-v3.json --output src/ordercloud
The codegen pipeline: OpenAPI JSON -> parser -> intermediate representation -> transformer -> Jinja2 templates -> Python source -> ruff format. Hand-written infrastructure (shared.py, base.py, auth.py, http.py, config.py, errors.py, middleware.py, sync_client.py) is preserved — only model and resource files are generated.
Development
git clone https://github.com/markcassidyconsulting/ordercloud-python.git
cd ordercloud-python
pip install -e ".[dev,examples,codegen]"
Running Tests
# Unit tests only (mocked HTTP, no network calls — fast)
pytest tests/ --ignore=tests/integration
# Unit tests with coverage
pytest tests/ --ignore=tests/integration --cov=ordercloud --cov-report=term-missing
# Lint and format
ruff check src/ tests/
ruff format --check src/ tests/
# Type checking
mypy src/
Integration Tests
Integration tests run against a live OrderCloud sandbox and are skipped automatically when credentials are not set. They never run by accident.
Setup:
- Create a
.envfile at the repo root (gitignored):
ORDERCLOUD_TEST_CLIENT_ID=your-sandbox-client-id
ORDERCLOUD_TEST_CLIENT_SECRET=your-sandbox-client-secret
ORDERCLOUD_TEST_BASE_URL=https://sandboxapi.ordercloud.io/v1
ORDERCLOUD_TEST_AUTH_URL=https://sandboxauth.ordercloud.io/oauth/token
- Run:
pytest tests/integration/ -v
The test suite is self-bootstrapping — it uses the SDK itself to create all test data from a single admin API client credential. All test resources use an inttest- ID prefix and are cleaned up automatically.
Why
ORDERCLOUD_TEST_*? TheTEST_prefix prevents the integration tests from running against a production OrderCloud instance if you happen to haveORDERCLOUD_CLIENT_IDset in your environment for normal SDK usage.
Test Suite
784 tests across 12 modules.
Unit tests (759) — mocked HTTP via respx, no network calls:
| Module | Tests | Purpose |
|---|---|---|
test_auth.py |
13 | OAuth2 token management |
test_http.py |
16 | HTTP client, error parsing, retries |
test_models.py |
28 | Model round-trips, enums, xp, ListPage |
test_resources.py |
22 | Representative resource operations |
test_resource_coverage.py |
632 | All 60 resources, all 632 operations |
test_sync_client.py |
48 | Sync wrapper, pagination |
Integration tests (25) — live sandbox, skipped when credentials are absent:
| Module | Tests | Purpose |
|---|---|---|
test_auth.py |
4 | Client credentials grant, token caching |
test_crud.py |
7 | Products, Buyers, Catalogs, Categories, Users |
test_pagination.py |
3 | Auto-pagination, search, list metadata |
test_query_params.py |
3 | Assignment lifecycle, DELETE with query params |
test_errors.py |
5 | Error parsing, structured API errors |
test_sync_client.py |
3 | Sync CRUD, pagination, errors |
Coverage (97% overall, 90% threshold enforced in CI):
| Module | Coverage |
|---|---|
auth.py |
100% |
client.py |
100% |
config.py |
100% |
errors.py |
100% |
http.py |
97% |
middleware.py |
100% |
sync_client.py |
100% |
resources/base.py |
100% |
models/shared.py |
100% |
| All 37 model modules | 100% |
Contributing
Bug reports and feature requests are welcome via GitHub Issues. If you're interested in the internals, the codegen pipeline in tools/codegen/ is a good starting point. See the Changelog for release history.
License
MIT
Project details
Release history Release notifications | RSS feed
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 ordercloud_python-2026.4.1.tar.gz.
File metadata
- Download URL: ordercloud_python-2026.4.1.tar.gz
- Upload date:
- Size: 135.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8431b5de80dccfe105da78959398056f3451eccd0ce5244f3fe52c6c37030638
|
|
| MD5 |
80412b2e9ac5cf4a1e3e1069a0d67678
|
|
| BLAKE2b-256 |
2a1b436d1d20f95e672867a76d28dc7b4ad6f84b8b0499e7723eda438e5a8a0b
|
Provenance
The following attestation bundles were made for ordercloud_python-2026.4.1.tar.gz:
Publisher:
release.yml on markcassidyconsulting/ordercloud-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ordercloud_python-2026.4.1.tar.gz -
Subject digest:
8431b5de80dccfe105da78959398056f3451eccd0ce5244f3fe52c6c37030638 - Sigstore transparency entry: 1286826839
- Sigstore integration time:
-
Permalink:
markcassidyconsulting/ordercloud-python@0d089b9ef2a7f81d5c1e77986163599a1263e0ab -
Branch / Tag:
refs/tags/v2026.4.1 - Owner: https://github.com/markcassidyconsulting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0d089b9ef2a7f81d5c1e77986163599a1263e0ab -
Trigger Event:
push
-
Statement type:
File details
Details for the file ordercloud_python-2026.4.1-py3-none-any.whl.
File metadata
- Download URL: ordercloud_python-2026.4.1-py3-none-any.whl
- Upload date:
- Size: 167.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
002e8a1dedf43925f5d35b992762dc02a5df93a1be87555e129c410204660c27
|
|
| MD5 |
114816de6d19dc637f2284b1383d29ba
|
|
| BLAKE2b-256 |
f5151fb0960586c37a10237c6065e709000fa2abebf3857ebcca41166d9fe988
|
Provenance
The following attestation bundles were made for ordercloud_python-2026.4.1-py3-none-any.whl:
Publisher:
release.yml on markcassidyconsulting/ordercloud-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ordercloud_python-2026.4.1-py3-none-any.whl -
Subject digest:
002e8a1dedf43925f5d35b992762dc02a5df93a1be87555e129c410204660c27 - Sigstore transparency entry: 1286826907
- Sigstore integration time:
-
Permalink:
markcassidyconsulting/ordercloud-python@0d089b9ef2a7f81d5c1e77986163599a1263e0ab -
Branch / Tag:
refs/tags/v2026.4.1 - Owner: https://github.com/markcassidyconsulting
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@0d089b9ef2a7f81d5c1e77986163599a1263e0ab -
Trigger Event:
push
-
Statement type: