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

# Catalog price, cost, and inventory

> Update OmniCommerce catalog selling prices, source-currency cost, and inventory. Price and inventory can optionally sync to marketplaces; cost never does.

Use the dedicated catalog endpoints when an integration needs to change
commercial values:

```text theme={null}
PATCH /api/v1/products/{productId}/price
PATCH /api/v1/products/{productId}/inventory
POST  /api/v1/products/cost
PATCH /api/v1/products/{productId}/cost
```

`productId` may be a simple-product UUID or a variant UUID. For a product with
variants, update each variant rather than the parent product. `GET
/api/v1/products/{productId}` on the parent returns the variant UUIDs in
`variants`.

## Authentication and organization scope

These endpoints require a bearer credential with `catalog:write`.
OmniCommerce derives `organizationId` from the credential. Do not include an
organization ID in the request body.

## Price and inventory: Omni only

Marketplace synchronization is opt-in. Omitting `syncToMarketplaces`, or setting
it to `false`, changes the OmniCommerce catalog without writing to a marketplace.

```bash Price theme={null}
curl --request PATCH \
  --url "https://omnicommerce.sg/api/v1/products/PRODUCT_UUID/price" \
  --header "Authorization: Bearer $OMNI_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "price": 29.90,
    "salePrice": 24.90,
    "currency": "SGD"
  }'
```

```bash Inventory theme={null}
curl --request PATCH \
  --url "https://omnicommerce.sg/api/v1/products/PRODUCT_UUID/inventory" \
  --header "Authorization: Bearer $OMNI_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "quantity": 125
  }'
```

## Price and inventory: synchronize marketplaces

Set `syncToMarketplaces` to `true` to queue durable marketplace work. Without a
`targets` array, OmniCommerce queues all enabled and published accounts attached
to the product.

To limit the write, pass exact account targets:

```json theme={null}
{
  "quantity": 125,
  "syncToMarketplaces": true,
  "targets": [
    { "platform": "shopee", "accountId": "shop-456" },
    { "platform": "lazada", "accountId": "seller-123" }
  ]
}
```

OmniCommerce validates every explicit `{ platform, accountId }` pair against the
product's enabled, published marketplace accounts before changing the catalog.
An unknown, disabled, or unpublished target rejects the entire request.

## Cost: Omni only

Catalog cost is **never** pushed to marketplaces. Do not send
`syncToMarketplaces` on the cost endpoints.

Store the **source** amount and currency. Omni does not convert the catalog
amount into the selling currency. When `costPriceCurrency` differs from the
product selling currency and `costPriceFxRate` is omitted, Omni quotes FX and
stores the quote on the product.

| Field | Meaning |
| - | - |
| `costPrice` | Source unit cost. `null` clears cost and FX. |
| `costPriceCurrency` | ISO currency of `costPrice` (for example `USD`). Defaults to the product selling currency. |
| `costPriceFxRate` | Optional units of selling currency per 1 `costPriceCurrency`. Quoted automatically when omitted. |
| `costPriceFxTo` | Must match the product selling currency when provided. |
| `costPriceFxAsOf` | Optional `YYYY-MM` or `YYYY-MM-DD` quote date. |

Prefer the batch endpoint for jobs. Each item targets `productId` **or** `sku`
(Omni `products.sku`). You may send both when they refer to the same product.
Maximum **200** updates per request.

```http theme={null}
POST /api/v1/products/cost
```

```bash theme={null}
curl --request POST \
  --url "https://omnicommerce.sg/api/v1/products/cost" \
  --header "Authorization: Bearer $OMNI_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "updates": [
      {
        "sku": "TLVBGLX83220",
        "costPrice": 578,
        "costPriceCurrency": "USD"
      }
    ]
  }'
```

Single-product sibling:

```http theme={null}
PATCH /api/v1/products/{productId}/cost
```

```bash theme={null}
curl --request PATCH \
  --url "https://omnicommerce.sg/api/v1/products/PRODUCT_UUID/cost" \
  --header "Authorization: Bearer $OMNI_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "costPrice": 578,
    "costPriceCurrency": "USD"
  }'
```

A successful cost write stores `product_data.costPrice`,
`product_data.costPriceCurrency`, and when currencies differ
`costPriceFxRate`, `costPriceFxFrom`, `costPriceFxTo`, `costPriceFxAsOf`, and
`costPriceFxSource` (`frankfurter` or `manual`). Approximate fallback rates are
rejected; pass `costPriceFxRate` or retry.

The batch response is `200` even when some rows fail. Top-level `success` is
true only when every update succeeded. Duplicate product IDs or SKUs fail after
the first occurrence. Unknown SKUs return
`SKU does not match an active, draft, or archived product.`

### List products, then write cost

To backfill cost for every listing on one marketplace, page the product list
first, keep `{ id, sku }`, then POST cost in chunks of 200.

```http theme={null}
GET /api/v1/products?marketplace=zalora&limit=50&offset=0
GET /api/v1/products?sku=TLVBGLX83220
GET /api/v1/products?sku=SKU-A&sku=SKU-B
```

`sku` is an **exact** `products.sku` match after trim. It is not the fuzzy `q` search. Repeat `sku` or comma-separate up to 50 values. Combined with `marketplace` and other filters using AND.

Follow `pagination.nextOffset` until `hasMore` is false. `limit` max is `50`.
Use `productId` from the list on the cost write so you do not resolve SKU twice.

API-key and OAuth requests take `organizationId` from the bearer credential.
Do not send `organizationId` on these URLs.

## Async response and job status

A marketplace **price or inventory** sync request returns `202 Accepted`.
`jobIds` contains the durable jobs, and `targets` shows the immediate queue
status for each account.

```json theme={null}
{
  "apiVersion": "developer_v1",
  "ok": true,
  "status": "accepted",
  "productId": "11111111-1111-4111-8111-111111111111",
  "jobId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
  "data": {
    "changed": true,
    "quantity": 125,
    "syncToMarketplaces": true,
    "jobIds": ["aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"],
    "targets": [
      {
        "platform": "lazada",
        "accountId": "seller-123",
        "status": "queued",
        "jobId": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
      }
    ],
    "emittedEvents": ["product.inventory.changed"]
  }
}
```

Poll each returned job with:

```text theme={null}
GET /api/v1/jobs/{jobId}
```

Inventory returns one durable outbox job per marketplace account. Price updates
return a durable batch job whose counters cover all target accounts.

Cost writes return `200` and do not create marketplace jobs.

## Webhook events

The local catalog transaction and outbound marketplace work are separate. A
successful local value change emits:

* `product.price.changed` for price, sale-price, currency, or catalog cost
  changes (`changedFields` includes `costPrice` for cost writes)
* `product.inventory.changed` for on-hand quantity changes
* `product.updated` for the same catalog transaction

Subscribe through the [Webhooks API](https://docs.omnicommerce.ai/api-reference.md) and see the normalized
[product webhook payloads](https://docs.omnicommerce.ai/webhooks/products.md). Marketplace job completion is
tracked through the job endpoint; it does not create a second catalog-change
webhook.

## Source ownership safeguards

The endpoints keep the same catalog rules as the OmniCommerce workspace:

* ERP-managed **selling** price or inventory must be changed in the ERP.
  Cost-only writes are allowed on ERP-managed products.
* Multi-source inventory must be changed through its source/location workflow.
* A sale price must remain below the list price.
* Active promotion assignments may prevent direct sale-price changes.

Use `GET /api/v1/products` or `GET /api/v1/catalog/items` with `catalog:read` to
discover product and variant UUIDs, SKUs, pricing guardrail inputs, promotion
context, and exact published marketplace identities before writing.


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