> ## 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.

# Orders API

> List, retrieve, create, and patch workspace and agentic orders.

Use the Developer Platform orders endpoints to list and retrieve workspace
orders, create native `agentic` orders from external systems, and patch agentic
order fields (status, created date, money, currency, and line items).

Settlements (fees, settlement status, marketplace net, and payment received) are
**not** order fields. Use the [Settlements API](https://docs.omnicommerce.ai/guides/settlements.md)
(`POST /api/v1/settlements`).

## Authentication

Send an OmniCommerce API key or OAuth client-credentials token:

```http theme={null}
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json
```

| Endpoint | Required scope |
| - | - |
| `GET /api/v1/orders` | `orders:read` |
| `GET /api/v1/orders/{orderId}` | `orders:read` |
| `POST /api/v1/orders` | `orders:write` |
| `PATCH /api/v1/orders/{orderId}` | `orders:write` |

OAuth clients must include the scope on the client. API keys created from organization settings receive all developer scopes by default.

Session-authenticated calls must include `organizationId` in the query (GET) or JSON body (POST/PATCH).

## Order status enum

Canonical statuses for create and patch (also used as listing filter groups):

| Status | Meaning |
| - | - |
| `pending` | Unpaid / not ready to fulfill |
| `to_ship` | Paid / ready for seller fulfillment |
| `shipped` | In transit / handed to carrier |
| `completed` | Delivered / finished |
| `return` | Return or refund in progress / done |
| `canceled` | Canceled or invalid |

Marketplace-imported orders may store platform-specific raw status strings; the developer write APIs only accept this enum.

## Pricing fields

Money fields on orders and line items:

| Field | Level | Meaning |
| - | - | - |
| `currency` | Order and/or line | ISO-4217 code (for example `SGD`, `MYR`, `PHP`, `HKD`). |
| `country` | Order | Marketplace country. When empty, derived from order currency when unambiguous (see below). |
| `totalAmount` | Order | Order total. On create, computed from line paid/unit prices × quantity when omitted. |
| `shippingFee` | Order | Shipping fee. |
| `discountAmount` | Order | Order-level discount. |
| `price` | Line | **Alias for `unitPrice`** (list / original unit price). Prefer `unitPrice` on new integrations. |
| `unitPrice` | Line | Per-unit **list / sticker** price before line discounts. |
| `paidPrice` | Line | Per-unit amount **actually charged** after discounts (preferred for GMV and totals). |
| `costPrice` | Line | Unit **COGS** (not a selling price). See below. |
| `costPriceCurrency` / `costCurrency` | Line | Currency of the raw `costPrice` (e.g. `USD`). |
| `costPriceFxRate` | Line | Manual FX: units of target per 1 unit of cost currency. |
| `costPriceFxTo` | Line | Target currency (defaults to line/order currency). |
| `costPriceFxAsOf` | Line | `YYYY-MM` or `YYYY-MM-DD` for Frankfurter auto-rate. |

### `price` vs `unitPrice` vs `paidPrice`

These three are **selling** prices on the line (per unit). They are **not** COGS and **not** marketplace fees.

| Field | Meaning | When to send |
| - | - | - |
| **`unitPrice`** | List / original unit price (before line discount) | Optional; useful for “was / now” or discount reporting |
| **`price`** | **Same as `unitPrice`** (legacy alias) | Optional; prefer `unitPrice` for new code |
| **`paidPrice`** | What the buyer **paid per unit** after discounts | **Preferred** for totals, GMV, and return refund estimates |

**How Omni picks a number for line money**

1. If **`paidPrice`** is set → use `paidPrice × quantity`
2. Else if **`unitPrice`** or **`price`** is set → use that × quantity
3. Else if **`productId`** is set on create → may resolve from the product catalog

**You do not need all three.** For most agentic / Zalora-style integrations, send **one** selling price:

```json theme={null}
{ "quantity": 1, "paidPrice": 80 }
```

or, if you only have a single price field in your system:

```json theme={null}
{ "quantity": 1, "price": 80 }
```

Use **both** list and paid when they differ:

```json theme={null}
{
  "quantity": 1,
  "unitPrice": 100,
  "paidPrice": 80
}
```

| Do not confuse with | |
| - | - |
| `totalAmount` | Order-level total (all lines + fees you pass) |
| `costPrice` | Unit COGS (cost of goods), not revenue |

These field descriptions are also published on **OpenAPI / Redocly** (`POST/PATCH /api/v1/orders` line schemas).

### `costPrice` (product-owned)

`costPrice` is **not** a first-class order table column. Its reusable catalog value is stored at:

`products.product_data.costPrice`

When you send `items[].costPrice` with a `productId`:

1. OmniCommerce **upserts** `product_data.costPrice` and `product_data.costPriceCurrency` on that product (**after FX**, in local/target currency).
2. The value is **snapshotted** on the order line as `metadata.costPrice`; this snapshot is canonical for historical order/P\&L economics.

To store **source-currency** catalog cost without converting the product row
(for example USD cost on an SGD listing), use
[`POST /api/v1/products/cost`](https://docs.omnicommerce.ai/guides/catalog-updates) instead of the order
line upsert. Order-line P\&L snapshots still convert into the order currency.

Rules:

* `costPrice` belongs on the **`items[]`** array (per line), not on the order root.
* **Omitted `costPrice` never clears** an existing product or line cost.
* When omitted on create with `productId`, the line snapshot may still resolve from the product catalog if a cost is already stored there.
* Product cost is only written when `costPrice` is **explicitly present** on the request.

### Cost currency and FX

P\&L treats `costPrice` as the **same unit as order revenue**. When invoice COGS is in another currency (e.g. USD) and the order is MYR/SGD/PHP/HKD:

```json theme={null}
{
  "costPrice": 120,
  "costPriceCurrency": "USD",
  "costPriceFxTo": "MYR",
  "costPriceFxRate": 4.45,
  "costPriceFxAsOf": "2026-07"
}
```

* Rate meaning: **1 USD = 4.45 MYR** → stored local cost `534`.
* If `costPriceCurrency` differs from the target and **`costPriceFxRate` is omitted**, Omni fetches the rate from [Frankfurter](https://api.frankfurter.dev/v1/latest?base=USD) (`getFxQuote` / ECB daily rates). Optional `costPriceFxAsOf` pins a historical date (`YYYY-MM-DD` or month `YYYY-MM`).
* Line metadata keeps an audit trail: `sourceCostPrice`, `sourceCostCurrency`, `costPriceFxRate`, `costPriceFxFrom`, `costPriceFxTo`, `costPriceFxAsOf`, `costPriceFxSource` (`manual` | `frankfurter` | `fallback` | `identity`).
* Alias: `costCurrency` = `costPriceCurrency`.
* Manual seller rates always win over auto FX.
* `costPriceFxTo` must match the effective line/order currency so COGS and revenue remain comparable.
* Cost currency and FX fields require an explicit `costPrice` on the same line. A currency-tagged catalog fallback is converted when its stored currency differs from the new line/order currency.
* When no rate can be resolved (unsupported pair, FX provider unreachable), the request fails with `400` **before** the order is written — retry with `costPriceFxRate`.

### Catalog fallbacks on create

When line `price` / `unitPrice` / `paidPrice` / `currency` are omitted and `productId` is provided, create resolves missing values from the product catalog (`products.price`, `product_data`, currency). Order total is then computed from the resolved line amounts when `totalAmount` is omitted.

## List orders

```http theme={null}
GET /api/v1/orders
```

### Query parameters

These match the workspace Orders listing page (`/app/{organizationId}/orders`):

| Parameter | Description |
| - | - |
| `organizationId` | Organization scope for session auth. Optional for single-org OAuth/API keys. |
| `q` | Search order number, external order ID, buyer fields, and line-item product/SKU fields. |
| `status` | Listing status filter: `to_ship`, `awaiting_collection`, `shipped`, `completed`, `pending`, `return`, `canceled`. `awaiting_collection` is packed orders waiting for carrier pickup. Shopify, Amazon, and eBay have no separate collection status. |
| `marketplace` | Comma-separated platforms, for example `shopee,lazada,tiktok`. |
| `country` | Comma-separated normalized country codes. |
| `store` | Comma-separated marketplace account IDs. |
| `sortBy` | Comma-separated sort tokens (multi-sort, max 6). Default: `created_desc`. Aliases: `latest` → `created_desc`, `oldest` → `created_asc`. |
| `dateFrom` | Inclusive created-date lower bound (`YYYY-MM-DD`). |
| `dateTo` | Inclusive created-date upper bound (`YYYY-MM-DD`). |
| `offset` | Zero-based pagination offset. Default `0`. |
| `limit` | Page size. Default `20`, max `50`. |

### Response

Each item includes order totals and line money fields when available:

```json theme={null}
{
  "items": [
    {
      "id": "00000000-0000-4000-8000-000000000001",
      "externalOrderId": "partner-order-1001",
      "status": "pending",
      "statusGroup": "pending",
      "marketplace": "agentic",
      "currency": "SGD",
      "totalAmount": 100,
      "totalLabel": "SGD 100.00",
      "totalQuantity": 2,
      "lines": [
        {
          "id": "line_1",
          "name": "Linen Wrap Dress",
          "quantity": 2,
          "sku": "DRESS-001",
          "productId": "11111111-1111-4111-8111-111111111111",
          "unitPrice": 50,
          "paidPrice": 50,
          "costPrice": 20,
          "currency": "SGD"
        }
      ]
    }
  ],
  "hasMore": false,
  "nextOffset": 20,
  "summary": {
    "totalCount": 1,
    "toShipCount": 0,
    "shippedCount": 0,
    "completedCount": 0,
    "pendingCount": 1,
    "returnCount": 0,
    "canceledCount": 0,
    "awaitingCollectionCount": 0
  },
  "pagination": {
    "offset": 0,
    "limit": 20,
    "total": 1,
    "hasMore": false,
    "nextOffset": null,
    "maxLimit": 50,
    "defaultLimit": 20
  }
}
```

Line `costPrice` is read from the order-line metadata snapshot (`metadata.costPrice`).

## Get an order

```http theme={null}
GET /api/v1/orders/{orderId}
```

Returns one organization-scoped order detail using the OmniCommerce UUID from
create or list. Marketplace orders and native `agentic` orders use the same
response contract.

Session-authenticated requests include the organization in the query:

```bash theme={null}
curl "https://omnicommerce.sg/api/v1/orders/00000000-0000-4000-8000-000000000001?organizationId=org_123" \
  -H "Authorization: Bearer $OMNI_API_KEY"
```

OAuth client-credentials tokens are already bound to one organization, so
`organizationId` is optional for those tokens.

The response wraps the detail in `data.order`. It includes stable and external
identity, exact marketplace account (`storeId`), raw/display status plus the
provider-neutral `statusGroup`, money fields with currency, buyer/contact and
address fields, line items, status history, cancellation/return context, and
finance/settlement detail when available.

```json theme={null}
{
  "ok": true,
  "apiVersion": "developer_v1",
  "operationId": "8f7e8f8f-2b9e-46e6-a2c0-50d0f4612b2d",
  "status": "completed",
  "warnings": [],
  "recommendations": [],
  "links": {
    "order": "/api/v1/orders/00000000-0000-4000-8000-000000000001?organizationId=org_123",
    "orders": "/api/v1/orders?organizationId=org_123"
  },
  "data": {
    "order": {
      "id": "00000000-0000-4000-8000-000000000001",
      "externalOrderId": "partner-order-1001",
      "externalOrderNumber": "PO-1001",
      "marketplace": "agentic",
      "storeId": null,
      "status": "pending",
      "statusGroup": "pending",
      "country": "sg",
      "currency": "SGD",
      "totalAmount": 100,
      "shippingFee": 5,
      "discountAmount": 0,
      "createdAt": "2026-08-29T02:00:00.000Z",
      "updatedAt": "2026-08-29T02:00:00.000Z",
      "lines": [
        {
          "id": "line_1",
          "externalOrderItemId": "partner-order-1001-1",
          "name": "Linen Wrap Dress",
          "quantity": 2,
          "status": "pending",
          "sku": "DRESS-001",
          "currency": "SGD",
          "unitPrice": 50,
          "paidPrice": 50,
          "lineTotal": 100,
          "costPrice": 20,
          "costPriceCurrency": "SGD",
          "productId": "11111111-1111-4111-8111-111111111111",
          "variantId": null
        }
      ]
    }
  }
}
```

The API returns `404` when the UUID does not exist in the authenticated
organization. It never falls back to an order with the same ID in another
organization.

## Create order

```http theme={null}
POST /api/v1/orders
```

Creates a native `agentic` order and line items through the same upsert path used by agentic checkout completion. Marketplace orders should continue to flow through marketplace import/webhook pipelines.

### Request body

```json theme={null}
{
  "organizationId": "org_123",
  "externalOrderId": "partner-order-1001",
  "externalOrderNumber": "PO-1001",
  "status": "to_ship",
  "currency": "SGD",
  "totalAmount": 100,
  "shippingFee": 5,
  "discountAmount": 0,
  "country": "SG",
  "buyer": {
    "name": "Alex Buyer",
    "email": "alex@example.com",
    "phone": "+6590000000"
  },
  "shippingAddress": {
    "line1": "123 Market Street",
    "city": "Singapore",
    "country": "SG"
  },
  "items": [
    {
      "productId": "11111111-1111-4111-8111-111111111111",
      "productName": "Linen Wrap Dress",
      "sku": "DRESS-001",
      "quantity": 2,
      "price": 50,
      "unitPrice": 50,
      "paidPrice": 50,
      "costPrice": 20,
      "currency": "SGD",
      "status": "to_ship"
    }
  ],
  "metadata": {
    "sourceSystem": "partner-erp"
  }
}
```

| Field | Required | Notes |
| - | - | - |
| `items` | Yes | At least one line item (max 100). |
| `items[].productName` | Yes | Display name for the line. |
| `items[].quantity` | Yes | Positive integer. |
| `items[].externalOrderItemId` | No | Stable line id for upserts and recon. Prefer partner per-SKU row id or marketplace item id. Generated as `{externalOrderId}-{index}` when omitted. |
| `items[].productId` | No | Product UUID. Enables catalog fallbacks and product `costPrice` upsert. |
| `items[].price` | No | Alias for `unitPrice`. |
| `items[].unitPrice` | No | Per-unit list price. Falls back to product catalog when omitted with `productId`. |
| `items[].paidPrice` | No | Per-unit paid price; preferred for totals when set. |
| `items[].costPrice` | No | Unit COGS. With `productId`, upserts product `product_data.costPrice`. Omitted value never clears existing cost. |
| `items[].sku` / `sellerSku` | No | SKU fields. `sellerSku` is used as a secondary Zalora recon match when unique on the order. |
| `items[].currency` | No | Line currency; falls back to order then product. |
| `items[].status` | No | Line status (canonical enum). Defaults to order status. |
| `status` | No | Canonical enum (default `pending`). |
| `currency` | No | Order currency (ISO-4217). Prefer **market** currency for marketplace recon (e.g. PHP for Zalora PH, not catalog SGD). |
| `country` | No | Marketplace country (`SG`, `singapore`, `PH`, `philippines`, …). When omitted/empty, Omni derives from order currency when unambiguous (see [Country from currency](#country-from-currency)). Shipping address `country` is used when top-level `country` is omitted. |
| `externalOrderId` | No | Stable upsert key (unique per org + agentic). For marketplace recon use the marketplace order number. Generated when omitted. |
| `externalOrderNumber` | No | Customer-facing number; defaults to `externalOrderId`. |
| `platform` | No | Always `agentic` for this endpoint. |
| `totalAmount` | No | When omitted, recomputed from **all** lines after upsert (safe for sequential multi-SKU posts). When set, stored as provided. |
| `shippingFee` | No | Shipping fee. |
| `discountAmount` | No | Order-level discount. |
| `orderCreatedAt` | No | ISO 8601 datetime with timezone. Stored as `order_created_at` (listing and analytics date filters). Defaults to now. Re-posting the same `externalOrderId` does **not** overwrite an existing created date — use PATCH. |

Re-posting the same `platform` + `externalOrderId` pair updates the existing order and returns HTTP `200` with `created: false`. Lines upsert by `externalOrderItemId`.

### Country from currency

When **order `country` is empty**, Omni may set country from **order currency** (and the same rule applies on PATCH and Zalora recon apply). Explicit `country` always wins.

| Currency | Derived country (stored canonical name) |
| - | - |
| `SGD` | `singapore` |
| `MYR` | `malaysia` |
| `PHP` | `philippines` |
| `HKD` | `hong kong` |
| `THB` | `thailand` |
| `VND` | `vietnam` |
| `IDR` | `indonesia` |
| `TWD` | `taiwan` |
| `BRL` | `brazil` |
| `USD` | *(not derived — ambiguous)* |

Workspace order listing and detail use the same fallback for display when stored country is empty.

### Multi-SKU / one-row-per-SKU partners (Zalora recon)

Omni models **one marketplace order → one Omni order**. Marketplace recon (for example Zalora Item Transaction Reports) matches the report order number to agentic `external_order_id`, then matches lines by `external_order_item_id` or unique Seller SKU on that order.

If your system stores **one row per SKU** that share a marketplace order id:

1. Always use the **marketplace order id** as `externalOrderId` (not your per-SKU order number).
2. Put your unique per-SKU id on `items[].externalOrderItemId`.
3. You may POST **one line at a time** with the same `externalOrderId` — lines upsert onto the same Omni order.
4. **Omit `totalAmount`** when appending lines so Omni recomputes the order total from all lines. Send `totalAmount` only when you know the full cart total.

Do **not** invent composite `externalOrderId` values like `MP-12345:SKU-A` for uniqueness — recon will not match the marketplace order number.

If you already created separate Omni orders per SKU with different external ids, create/upsert again using the marketplace order id as `externalOrderId` and stable line `externalOrderItemId`s (preferred going forward). Do not set the same marketplace id on multiple Omni order shells via PATCH — that still returns 409.

#### Example: Zalora cart with 2 SKUs

Your system (one DB row per SKU):

| Your order # | Marketplace order id | SKU | Paid |
| - | - | - | - |
| `ORD-9001` | `3012345678` | `SKU-A` | 50 SGD |
| `ORD-9002` | `3012345678` | `SKU-B` | 40 SGD |

**Wrong** — two Omni orders (or same `externalOrderId` on two shells → `409`):

```http theme={null}
POST /api/v1/orders
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "externalOrderId": "ORD-9001",
  "currency": "SGD",
  "items": [
    {
      "externalOrderItemId": "ORD-9001",
      "sku": "SKU-A",
      "productName": "Item A",
      "quantity": 1,
      "paidPrice": 50
    }
  ]
}
```

```http theme={null}
POST /api/v1/orders
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "externalOrderId": "ORD-9002",
  "currency": "SGD",
  "items": [
    {
      "externalOrderItemId": "ORD-9002",
      "sku": "SKU-B",
      "productName": "Item B",
      "quantity": 1,
      "paidPrice": 40
    }
  ]
}
```

Zalora report order `3012345678` will not match either shell.

**Right** — one Omni order; your row ids become line ids. Same marketplace id on every push; omit `totalAmount` so Omni sums all lines:

```http theme={null}
POST /api/v1/orders
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "externalOrderId": "3012345678",
  "externalOrderNumber": "3012345678",
  "currency": "SGD",
  "status": "to_ship",
  "items": [
    {
      "externalOrderItemId": "ORD-9001",
      "sku": "SKU-A",
      "sellerSku": "SKU-A",
      "productName": "Item A",
      "quantity": 1,
      "paidPrice": 50
    }
  ]
}
```

First response (`201`, `created: true`):

```json theme={null}
{
  "ok": true,
  "status": "completed",
  "data": {
    "orderId": "11111111-1111-4111-8111-111111111111",
    "externalOrderId": "3012345678",
    "created": true,
    "platform": "agentic",
    "totalAmount": 50
  }
}
```

```http theme={null}
POST /api/v1/orders
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "externalOrderId": "3012345678",
  "externalOrderNumber": "3012345678",
  "currency": "SGD",
  "status": "to_ship",
  "items": [
    {
      "externalOrderItemId": "ORD-9002",
      "sku": "SKU-B",
      "sellerSku": "SKU-B",
      "productName": "Item B",
      "quantity": 1,
      "paidPrice": 40
    }
  ]
}
```

Second response (`200`, `created: false` — same order, second line appended):

```json theme={null}
{
  "ok": true,
  "status": "completed",
  "data": {
    "orderId": "11111111-1111-4111-8111-111111111111",
    "externalOrderId": "3012345678",
    "created": false,
    "platform": "agentic",
    "totalAmount": 90
  }
}
```

Result in Omni:

| Field | Value |
| - | - |
| One order `externalOrderId` | `3012345678` (matches Zalora order number) |
| Line 1 `externalOrderItemId` | `ORD-9001` (your unique row) |
| Line 2 `externalOrderItemId` | `ORD-9002` |
| `totalAmount` | `90` (recomputed from both lines) |

You can also send **both lines in one request** if your push job can group by marketplace order id:

```http theme={null}
POST /api/v1/orders
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "externalOrderId": "3012345678",
  "externalOrderNumber": "3012345678",
  "currency": "SGD",
  "status": "to_ship",
  "items": [
    {
      "externalOrderItemId": "ORD-9001",
      "sku": "SKU-A",
      "sellerSku": "SKU-A",
      "productName": "Item A",
      "quantity": 1,
      "paidPrice": 50
    },
    {
      "externalOrderItemId": "ORD-9002",
      "sku": "SKU-B",
      "sellerSku": "SKU-B",
      "productName": "Item B",
      "quantity": 1,
      "paidPrice": 40
    }
  ]
}
```

### Response

```json theme={null}
{
  "ok": true,
  "apiVersion": "developer_v1",
  "operationId": "8f7e8f8f-2b9e-46e6-a2c0-50d0f4612b2d",
  "status": "completed",
  "warnings": [],
  "recommendations": [],
  "links": {
    "order": "/api/v1/orders?organizationId=org_123&q=partner-order-1001"
  },
  "data": {
    "orderId": "00000000-0000-4000-8000-000000000001",
    "externalOrderId": "partner-order-1001",
    "created": true,
    "platform": "agentic",
    "totalAmount": 100
  }
}
```

| Response field | Notes |
| - | - |
| `orderId` | OmniCommerce order UUID. Stable across re-posts of the same `externalOrderId`. |
| `externalOrderId` | Echo of the upsert key. |
| `created` | `true` on first insert (`201`); `false` on upsert (`200`). |
| `platform` | Always `agentic` for this endpoint. |
| `totalAmount` | Final order total after create/upsert (explicit request value, or recomputed from all lines when omitted). |

## Update order

```http theme={null}
PATCH /api/v1/orders/{orderId}
```

Updates a native **agentic** order. Supports external order identifiers, status, created date, currency, country, money fields, metadata, and line-item upserts (`externalOrderItemId`, `price` / `unitPrice` / `paidPrice` / `costPrice` / `currency` / `status` / `quantity`, and more).

Marketplace orders are rejected with `409` — those fields are marketplace-owned. Use marketplace tooling or the workspace order-actions flow for them.

At least one of `externalOrderId`, `externalOrderNumber`, `status`, `currency`, `country`, `totalAmount`, `shippingFee`, `discountAmount`, `metadata`, `orderCreatedAt`, or `items` is required.

When `country` is omitted and the order’s stored country is empty, Omni fills country from the resulting order currency (same map as create — see [Country from currency](#country-from-currency)).

**Line upserts:** matching `externalOrderItemId` (or unique `productId`) updates the line. A new `externalOrderItemId` with `productName` + `quantity` **inserts** a line (multi-SKU append on an existing order). When `totalAmount` is omitted and lines change, Omni recomputes the total from all lines.

### Path parameters

| Parameter | Description |
| - | - |
| `orderId` | OmniCommerce order UUID from create or list. |

### Request body

Status-only:

```json theme={null}
{
  "organizationId": "org_123",
  "status": "shipped",
  "metadata": {
    "trackingNumber": "SG123456789",
    "carrier": "Ninja Van"
  }
}
```

Money, currency, and line pricing (status optional):

```json theme={null}
{
  "organizationId": "org_123",
  "currency": "SGD",
  "totalAmount": 120.5,
  "shippingFee": 5,
  "items": [
    {
      "externalOrderItemId": "partner-order-1001-1",
      "price": 110,
      "costPrice": 45,
      "currency": "SGD"
    }
  ]
}
```

Correct created date:

```json theme={null}
{
  "organizationId": "org_123",
  "orderCreatedAt": "2026-03-15T08:00:00.000Z"
}
```

Repair identifiers on an imported order:

```json theme={null}
{
  "organizationId": "org_123",
  "externalOrderId": "MY-223347629SG",
  "externalOrderNumber": "MY-223347629SG",
  "items": [
    {
      "currentExternalOrderItemId": "dev-order-1",
      "externalOrderItemId": "10817558"
    }
  ]
}
```

| Field | Required | Notes |
| - | - | - |
| *(at least one mutable field)* | Yes | Empty body is rejected. |
| `externalOrderId` | No | Replacement stable order identifier. Must be unique among agentic orders in the organization. |
| `externalOrderNumber` | No | Replacement customer-facing order number. |
| `status` | No | Canonical enum. Optional when patching money/lines/metadata. |
| `currency` | No | Order currency (ISO-4217). |
| `country` | No | Marketplace country. When omitted and stored country is empty, derived from order currency when unambiguous. Explicit value always wins. |
| `totalAmount` | No | Order total. When omitted and line prices change, recomputed from lines. |
| `shippingFee` | No | Shipping fee. |
| `discountAmount` | No | Order-level discount. |
| `orderCreatedAt` | No | ISO 8601 datetime with timezone. Overwrites stored `order_created_at`. Omitted value is left unchanged. |
| `organizationId` | Session multi-org only | Optional for single-org API keys / OAuth clients. |
| `metadata` | No | Shallow merge into existing order metadata (does not replace the whole object). |
| `items` | No | Partial line updates (1–100). |

### Line patch identity

Each `items[]` entry must identify a line with:

| Field | Notes |
| - | - |
| `currentExternalOrderItemId` | Current line identifier. Use this selector when replacing `externalOrderItemId`. |
| `externalOrderItemId` | Selects the current line when `currentExternalOrderItemId` is omitted. When both are present, this is the replacement identifier. If no line matches and `productName` + `quantity` are set, a **new line is inserted**. |
| `productId` | Allowed when that product appears on exactly one line of the order. |

Each patch line must also include at least one field to update (`price`, `unitPrice`, `paidPrice`, `costPrice`, `currency`, `status`, `quantity`, etc.), or `productName` + `quantity` when inserting a new line.

Omitted `costPrice` on a line patch **does not** clear product or line cost. Explicit `costPrice` with a resolved product id upserts the product catalog cost.

#### Partial vs full return (line status)

Canonical line/order status `return` marks a return. **Returns are line-level**; order status is a roll-up.

**Partial return** — one of several lines returned (order stays sold for remaining lines):

```http theme={null}
PATCH /api/v1/orders/{orderId}
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "items": [
    {
      "externalOrderItemId": "ORD-9001",
      "status": "return"
    }
  ]
}
```

Response includes roll-up fields:

| Field | Partial example |
| - | - |
| `returnState` | `partial` |
| `hasReturnSignal` | `true` |
| `returnRefundAmount` | ≥ revenue of returned line(s) (`paidPrice × qty`) |
| `includeInSalesMetrics` | `true` (kept lines still count) |
| `status` / `statusGroup` | **Unchanged** unless you set top-level `status` |

P\&L and product economics treat returned lines as **sold qty 0**. Ledger fees with `external_order_item_id` attribute to that line; remaining fees pro-rate by sold line revenue.

**Full return** — every line returned (or set order status explicitly):

```json theme={null}
{
  "items": [
    { "externalOrderItemId": "ORD-9001", "status": "return" },
    { "externalOrderItemId": "ORD-9002", "status": "return" }
  ]
}
```

When **all** lines are `return` and you did **not** send a different top-level `status`, Omni sets order `status` to `return`, `returnState` to `full`, and `returnRefundAmount` to at least the full order total so retained revenue is zero.

You can still force full-order return with:

```json theme={null}
{ "status": "return" }
```

That top-level status **marks every line** `return`, zeros line `paymentReceived`, rolls up refund/sales flags, and restocks product inventory **only when** the line had inventory deducted on sale (`metadata.inventoryDeductedAt`). Line selection by `productId` works the same as by `externalOrderItemId` (must be unique on the order).

PATCH responses and each `GET /api/v1/orders` item surface unit-economics fields:

| Field | Meaning |
| - | - |
| `paymentReceived` | Sum of non-returned line payment-received; null until every active line is reconciled |
| `orderCogs` | Sum of sold line `metadata.costPrice × qty`; null while any active line lacks COGS |
| `orderProfit` | `paymentReceived − orderCogs`; null if either input is incomplete |
| `orderMargin` | `orderProfit / paymentReceived` (null if no payment) |

Optional per-line refund override: set `items[].metadata.returnRefundAmount` before/with the status patch; roll-up prefers that over `paidPrice × qty`.

#### Example: append a second SKU via PATCH

After creating order `3012345678` with line `ORD-9001`, append line `ORD-9002` without re-posting the whole cart:

```http theme={null}
PATCH /api/v1/orders/11111111-1111-4111-8111-111111111111
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "items": [
    {
      "externalOrderItemId": "ORD-9002",
      "productName": "Item B",
      "sku": "SKU-B",
      "sellerSku": "SKU-B",
      "quantity": 1,
      "paidPrice": 40,
      "currency": "SGD"
    }
  ]
}
```

Response includes recomputed `totalAmount` (e.g. `90` when the first line was `50`). Omit top-level `totalAmount` so Omni sums all lines.

Update an existing line (same id) by sending only the fields to change:

```http theme={null}
PATCH /api/v1/orders/11111111-1111-4111-8111-111111111111
Authorization: Bearer $OMNI_API_KEY
Content-Type: application/json

{
  "items": [
    {
      "externalOrderItemId": "ORD-9002",
      "paidPrice": 38,
      "quantity": 2
    }
  ]
}
```

### Response

```json theme={null}
{
  "ok": true,
  "apiVersion": "developer_v1",
  "operationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "status": "completed",
  "warnings": [],
  "recommendations": [],
  "links": {
    "order": "/api/v1/orders?organizationId=org_123&q=partner-order-1001"
  },
  "data": {
    "orderId": "00000000-0000-4000-8000-000000000001",
    "externalOrderId": "partner-order-1001",
    "externalOrderNumber": "PO-1001",
    "platform": "agentic",
    "previousStatus": "to_ship",
    "status": "shipped",
    "statusGroup": "shipped",
    "currency": "SGD",
    "totalAmount": 120.5,
    "shippingFee": 5,
    "discountAmount": 0,
    "orderCreatedAt": "2026-03-15T08:00:00.000Z",
    "changed": true,
    "changedFields": [
      "status",
      "currency",
      "totalAmount",
      "items.unitPrice",
      "items.costPrice"
    ]
  }
}
```

`changed` is `false` when the request is a no-op. `changedFields` lists what actually changed.

## Errors

* `400` — invalid JSON, validation failure, unknown status, missing patch fields, or line identity failure.
* `401` — missing or invalid bearer token / session.
* `403` — missing `orders:read` or `orders:write` scope.
* `404` — order not found for this organization (`PATCH` only).
* `409` — updates not supported for marketplace-owned orders, or an external identifier is already in use.
* `429` — developer API rate limit exceeded.
* `500` — unexpected server failure.

## Related docs

* [Developer Platform](https://docs.omnicommerce.ai/quickstart.md)
* [OpenAPI reference](https://docs.omnicommerce.ai/api-reference.md)
* [Authentication](https://docs.omnicommerce.ai/authentication.md)
* Agentic checkout endpoints: `POST /api/v1/checkout-sessions` in the [API reference](https://docs.omnicommerce.ai/api-reference.md)


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