Skip to main content

Idiomatic Python SDK for Sitecore OrderCloud

Project description

ordercloud-python

CI CodeQL codecov OpenSSF Scorecard Python PyPI License: MIT

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 clientsasync with OrderCloudClient(...) or with 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 propertiesProduct[MyXpModel] gives you type-safe access to OrderCloud's xp fields.
  • Auto-paginationasync for product in paginate(client.products.list) handles page iteration automatically.
  • Retry with backoff — configurable retries on 429/5xx with exponential backoff and Retry-After support.
  • Middleware hooks — intercept requests and responses for logging, metrics, or header injection.
  • Structured logging — standard Python logging module, DEBUG/WARNING levels.
  • Full type annotationspy.typed marker 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:

  1. Create a .env file 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
  1. 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_*? The TEST_ prefix prevents the integration tests from running against a production OrderCloud instance if you happen to have ORDERCLOUD_CLIENT_ID set 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ordercloud_python-2026.4.1.tar.gz (135.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ordercloud_python-2026.4.1-py3-none-any.whl (167.8 kB view details)

Uploaded Python 3

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

Hashes for ordercloud_python-2026.4.1.tar.gz
Algorithm Hash digest
SHA256 8431b5de80dccfe105da78959398056f3451eccd0ce5244f3fe52c6c37030638
MD5 80412b2e9ac5cf4a1e3e1069a0d67678
BLAKE2b-256 2a1b436d1d20f95e672867a76d28dc7b4ad6f84b8b0499e7723eda438e5a8a0b

See more details on using hashes here.

Provenance

The following attestation bundles were made for ordercloud_python-2026.4.1.tar.gz:

Publisher: release.yml on markcassidyconsulting/ordercloud-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ordercloud_python-2026.4.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ordercloud_python-2026.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 002e8a1dedf43925f5d35b992762dc02a5df93a1be87555e129c410204660c27
MD5 114816de6d19dc637f2284b1383d29ba
BLAKE2b-256 f5151fb0960586c37a10237c6065e709000fa2abebf3857ebcca41166d9fe988

See more details on using hashes here.

Provenance

The following attestation bundles were made for ordercloud_python-2026.4.1-py3-none-any.whl:

Publisher: release.yml on markcassidyconsulting/ordercloud-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page