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 639 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.
  • 791 tests, 97% coverage — 766 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 639 operations in the OrderCloud API. Models and resource clients are generated from the official OpenAPI v3 spec (version 1.0.454).

Core Commerce

Resource Operations Highlights
Products 18 CRUD, variants, specs, suppliers, assignments
Orders 30 CRUD, submit, approve, decline, cancel, complete, forward, split, ship, repeat, 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 10 CRUD, assignments, generated codes
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 45 Full sync infrastructure (incl. catalogs)
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

791 tests across 12 modules.

Unit tests (766) — 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 639 All 60 resources, all 639 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, feature requests, and pull requests are welcome. See CONTRIBUTING.md for development setup, running tests, code generation, and PR guidelines. 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.6.1.tar.gz (138.5 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.6.1-py3-none-any.whl (168.9 kB view details)

Uploaded Python 3

File details

Details for the file ordercloud_python-2026.6.1.tar.gz.

File metadata

  • Download URL: ordercloud_python-2026.6.1.tar.gz
  • Upload date:
  • Size: 138.5 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.6.1.tar.gz
Algorithm Hash digest
SHA256 f2c5365af870a3d316d10c14aa13927a3d07819b4f41aa107cc66bae9d68f669
MD5 d9eb46b555d42d41d53581637a70df2f
BLAKE2b-256 2e449f5635d0aa059d3dd6988150e2d56cb3b41f775b712109be99a2c72248a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for ordercloud_python-2026.6.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.6.1-py3-none-any.whl.

File metadata

File hashes

Hashes for ordercloud_python-2026.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 9baea9e80bdabab24bc2bc9b06f2c50ae6b05ea92e6e6425df691a70d715a8a7
MD5 fcb9955d588d59316de80ac1e33db1c6
BLAKE2b-256 d4bce0924a27ed507eeb8190e51142997a475866b36eafac789f68b96864bedd

See more details on using hashes here.

Provenance

The following attestation bundles were made for ordercloud_python-2026.6.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