> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omnicommerce.sg/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Sync and async Python client for every OmniCommerce REST endpoint.

The Python SDK (`omnicommerce`) is generated from the same public OpenAPI spec as the [TypeScript SDK](https://docs.omnicommerce.sg/sdk/typescript.md). Every documented REST operation is a resource method. Responses are parsed JSON: objects are dicts, arrays are lists, and an empty body is `None`.

## Install

Install the SDK from PyPI:

```bash theme={null}
python3 -m pip install omnicommerce
```

Requires Python 3.10+.

```python theme={null}
import os
from omnicommerce import AsyncOmni, Omni
```

## Authenticate

Use an organization API key (`omni_sk_...`) as a bearer token. Do not send `organization_id` on API-key requests.

```python theme={null}
client = Omni(api_key=os.environ["OMNI_API_KEY"])
```

Omit `api_key` to read `OMNI_API_KEY`. Override the host with `base_url` or `environment` (`https://omnicommerce.sg` by default). `OmniEnvironment.LOCAL` is `http://localhost:3000`. See [Authentication](https://docs.omnicommerce.sg/authentication.md).

```python theme={null}
from omnicommerce import Omni, OmniEnvironment

client = Omni(
    api_key=os.environ["OMNI_API_KEY"],
    environment=OmniEnvironment.LOCAL,
    timeout_in_seconds=60,
    max_retries=2,
)
```

Use the client as a context manager so the HTTP connection closes. `AsyncOmni` is the async client and uses `async with`.

## Resource clients

Each call takes keyword arguments. Path, query, and JSON fields keep their wire names in snake\_case: `productId` is `product_id`, `minPrice` is `min_price`.

```python theme={null}
products = client.products.list(q="lamp")

job = client.products.create(
    product={
        "sku": "SKU-100",
        "title": "Sample product",
        "price": 29.9,
        "currency": "SGD",
        "images": ["https://cdn.example.com/product.jpg"],
    }
)
client.jobs.wait(job_id=job["jobId"])

publish = client.products.publish(product_id="prod_123", visibility="live")
client.jobs.wait(job_id=publish["jobId"])

delist = client.products.delist(
    product_id="prod_123",
    marketplaces=["shopee", "lazada"],
)
client.jobs.wait(job_id=delist["jobId"])
```

Per-request overrides:

```python theme={null}
from omnicommerce import RequestOptions

client.orders.get(
    order_id="ord_123",
    request_options=RequestOptions(timeout_in_seconds=15),
)
```

`client.with_raw_response.orders.get(...)` returns `data`, `status_code`, `headers`, and `url`.

```python theme={null}
async with AsyncOmni(api_key=os.environ["OMNI_API_KEY"]) as client:
    products = await client.products.list(q="lamp")
```

## Publish selected fields

Pass `marketplaces` to choose where to publish/update. Omit it to use every enabled or already listed marketplace. Pass `update_fields` to publish only those saved product fields, matching the publish popover. Selected-field updates require an existing listing and assigned store; other fields and listing visibility stay unchanged. Omit fields for a full publish.

```python theme={null}
publish = client.products.publish(
    product_id="prod_123",
    marketplaces=["shopee", "lazada"],
    update_fields=["name", "description", "images"],
)
for target in publish["data"]["results"]:
    if target.get("jobId"):
        client.jobs.wait(job_id=target["jobId"])
    for correlation in target.get("correlations", []):
        client.jobs.wait(job_id=correlation["id"])
```

Check `ok` and every target result: valid marketplaces can queue while another fails. Price and inventory selections return their durable jobs in the same results. Queued work is not proof of a live marketplace change. See the [API reference](https://docs.omnicommerce.sg/api-reference.md) for supported fields and marketplace capability limits.

## Product inventory

Response keys keep their API casing even though method arguments use snake\_case. Read `page["items"]` and each row's `stockQuantity` and `variantCount` (above 0 means the product has variants).

```python theme={null}
page = client.products.list(q="lamp")
for product in page["items"]:
    quantity = product.get("stockQuantity")
    print(product["id"], "Unknown stock" if quantity is None else quantity)
```

`stockQuantity` is catalog stock, matching the Products page. Parent rows already aggregate their variants; virtual bundles already account for component stock and any manual cap. Preserve null or missing values as unknown. Do not read `inventoryQuantity`, `inventory.quantity`, or `stock_quantity`, and do not sum a missing `variants` array. See [Product inventory](https://docs.omnicommerce.sg/sdk/typescript.md#product-inventory) for quantity semantics.

## Market price comparison

Compare up to 50 simple product or variant IDs. This read-only API requires `catalog:read` and returns a durable job. `AsyncOmni` exposes the same methods with `await`.

```python theme={null}
queued = client.products.compare_prices(
    product_ids=["cc87d195-097a-4a5a-8229-cd989ce98d0f"],
)
client.jobs.wait(job_id=queued["jobId"], timeout_in_seconds=1800)
completed = client.products.get_price_comparison(job_id=queued["jobId"])
for product in (completed.get("result") or {}).get("results", []):
    for market in product["markets"]:
        print(product["productId"], market["country"], market["currency"],
              market["currentPrice"], market["comparison"], market.get("error"))
```

Omit `gl` to compare every recorded listing market. Global Zalora uses each country's listing price and currency, including Hong Kong and Taiwan. An India Shopify store selling in SGD is researched in Singapore. Pass `gl="sg"` to restrict research to Singapore. Use variant IDs for products with variants.

Inspect the final `result.success` and each market's `success`, `priceSource`, `priceAsOf`, and errors. Missing exact published prices remain explicit failures; catalog research may still be returned. Unlisted products use catalog pricing. No monetary values are combined across currencies. Persisted price evidence can be stale; refresh listings as needed and verify shopping variants and pack sizes. A client wait timeout does not cancel the job. See the [API reference](https://docs.omnicommerce.sg/api-reference.md) for the canonical contract.

## Coverage

| Client | Examples |
| - | - |
| `client.brandsgateway` | `import_products` |
| `client.products` | `list`, `create`, `get`, `update`, `delete`, `publish`, `get_listings`, `delist`, `bulk_import`, `enrich`, `upload_image` |
| `client.orders` | `list`, `create`, `get`, `update`, `get_action_options`, `queue_action`, `list_actions` |
| `client.documents` | `upload`, `get` |
| `client.reviews` | `list`, `get`, `reply` |
| `client.analytics` | `get_overview`, `get_traffic`, `get_products`, `get_pricing`, `get_ads`, `get_orders`, `get_content`, `get_profit`, `get_ai_traffic`, `get_stores`, `get_reports` |
| `client.promotions` | `list`, `create`, `get`, `update`, `delete`, `retry` |
| `client.price_books` | `list`, `create`, `get`, `update`, `delete`, `preview` |
| `client.aop` | `create`, `execute`, `get_config`, `update_config`, `retry` |
| `client.agents.general` | `invoke`, `start`, `get_run` |
| `client.jobs` | `get`, `cancel`, `wait` |
| `client.webhooks` | `list`, `create` |
| `client.checkout_sessions` | `create`, `get`, `update`, `complete`, `cancel` |
| `client.ucp` | `checkout_sessions`, `orders` |
| `client.oauth` | `token`, `revoke` |

Looks, returns, settlements, marketplaces, search, evaluate, monitor, sync, Zalora, and the remaining documented tags follow the same pattern.

`client.agents.general.invoke` waits for the Omni agent. Pass `request_options=RequestOptions(timeout_in_seconds=780)` on that call. `start` returns immediately and `get_run(run_id=...)` polls the run. See [Omni agent API](https://docs.omnicommerce.sg/guides/agent.md).

## File uploads

Upload methods (`documents.upload`, `products.upload_image`) send multipart/form-data. Pass an open binary file, or a `(filename, bytes, content_type)` tuple.

```python theme={null}
with open("manual.pdf", "rb") as file:
    client.documents.upload(file=file, document_type="manual")
```

## Errors

Non-2xx responses raise `OmniError` subclasses. See [SDK errors](https://docs.omnicommerce.sg/sdk/errors.md).

GET, HEAD, OPTIONS, PUT, and DELETE retry on 5xx and network failures. 408 and 429 retry for every method. POST and PATCH are not retried on 5xx.

## Webhooks

```python theme={null}
from omnicommerce import construct_event

event = construct_event(raw_body, request.headers, os.environ["OMNI_WEBHOOK_SECRET"])
```

HMAC-SHA256 over `{timestamp}.{raw_body}` compared to `X-Omni-Signature`. See [Webhooks](https://docs.omnicommerce.sg/webhooks/overview.md).

## Regenerate

From this repository:

```bash theme={null}
yarn sdk:generate
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.