OptiWMS API

API reference

OptiWMS API 1.0.0. Every operation and store callback from the OpenAPI document.

Base URL https://api.optiwms.com/store/v1/. Field names are camelCase on the OptiWMS API; dashboard notification webhooks use snake_case and a separate signing scheme (see Webhooks).

Calls into OptiWMS

Signed requests your store sends to api.optiwms.com.

Check the connection and the signing

Answers when the signature verifies, with the integration's name and the warehouse its orders go to. Make this call work before any other: a 401 here means the signing is wrong, not the data. `warehouse` is null when no warehouse is chosen for the integration. Orders then go to the organization's first warehouse, so ask an administrator to choose one in the dashboard before you send orders.

Response 200

The signature verified.

FieldTypeRequiredDescription
integrationobjectyes
warehouseobjectyesThe warehouse this integration's orders go to; null when none is chosen.
serverTimestringyesAn ISO 8601 time with its offset.
Response 200: Signed correctly
{
  "integration": {
    "id": "42",
    "name": "Our web store"
  },
  "warehouse": {
    "id": "3",
    "name": "Sydney DC"
  },
  "serverTime": "2026-10-27T09:00:00Z"
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Create or update variants

Sends up to 250 variants. Send a variant whenever it is created or changes in your store; sending one that has not changed is safe and answers `unchanged`, so a retry after a timeout does no harm. OptiWMS matches each variant to its own products by SKU. A SKU it already holds is linked to that product; a new SKU creates a product. A variant without a SKU is created under a placeholder SKU and linked by its variantId, so orders for it still import. Draft and archived variants are skipped unless the integration is set to include unpublished products. When product sync is off for the integration in the dashboard, every variant is skipped. The answer lists, for each variant, what happened, in the order sent. A SKU OptiWMS already holds for a different product is skipped and raised as a question in the dashboard for staff to settle.

Request body

FieldTypeRequiredDescription
variantsarrayyes
Request example: A tee in two sizes and a gift card
{
  "variants": [
    {
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "productTitle": "Organic tee",
      "variantTitle": "Black / M",
      "options": [
        {
          "name": "Color",
          "value": "Black"
        },
        {
          "name": "Size",
          "value": "M"
        }
      ],
      "barcode": "9300000000017",
      "price": "29.95",
      "weight": {
        "value": 220,
        "unit": "g"
      },
      "imageUrl": "https://shop.example.com/images/tee-black.jpg",
      "status": "active",
      "ships": true,
      "tracksStock": true
    },
    {
      "productId": "201",
      "variantId": "302",
      "sku": "TEE-BLK-L",
      "productTitle": "Organic tee",
      "variantTitle": "Black / L",
      "options": [
        {
          "name": "Color",
          "value": "Black"
        },
        {
          "name": "Size",
          "value": "L"
        }
      ],
      "barcode": "9300000000024",
      "price": "29.95",
      "weight": {
        "value": 240,
        "unit": "g"
      },
      "imageUrl": "https://shop.example.com/images/tee-black.jpg",
      "status": "active",
      "ships": true,
      "tracksStock": true
    },
    {
      "productId": "250",
      "variantId": "250",
      "sku": "GIFT-50",
      "productTitle": "Gift card $50",
      "price": "50.00",
      "status": "active",
      "ships": false,
      "tracksStock": false
    }
  ]
}
Request example: A variant without its id and with an unknown status
{
  "variants": [
    {
      "productId": "201",
      "sku": "TEE-BLK-M",
      "productTitle": "Organic tee",
      "price": "29.95",
      "status": "published"
    }
  ]
}

Response 200

Every variant was handled; `results` says how, in the order sent.

FieldTypeRequiredDescription
resultsarrayyes
Response 200: Two variants created, the gift card skipped
{
  "results": [
    {
      "variantId": "301",
      "outcome": "created"
    },
    {
      "variantId": "302",
      "outcome": "created"
    },
    {
      "variantId": "250",
      "outcome": "skipped",
      "reason": "Not shipped. Gift cards, digital products and services are not warehouse stock."
    }
  ]
}
Response 200: The same call sent again
{
  "results": [
    {
      "variantId": "301",
      "outcome": "unchanged"
    },
    {
      "variantId": "302",
      "outcome": "unchanged"
    },
    {
      "variantId": "250",
      "outcome": "skipped",
      "reason": "Not shipped. Gift cards, digital products and services are not warehouse stock."
    }
  ]
}

Response 400

The body is not valid. Nothing was changed; fix the fields listed in `details` and send the whole call again. More than 250 variants is also refused here: split them over several calls.

FieldTypeRequiredDescription
errorobjectyes
Response 400: invalid
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Some fields of the request body are missing or not valid. Fix the fields listed in details and send the call again.",
    "details": [
      {
        "path": "variants[0].variantId",
        "message": "Required: your store's id for this variant; for a product sold in one form, send the product id again."
      },
      {
        "path": "variants[0].status",
        "message": "Required: one of active, draft or archived."
      }
    ],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 413

The body is over 1 MiB. A request that declares its length gets this small JSON body and no request id; one sent without a length gets an empty body. Send fewer variants per call.

FieldTypeRequiredDescription
errorstringyes
Response 413: tooLarge
{
  "error": "request body too large: this endpoint accepts at most 1 MiB"
}

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Mark variants or products as removed from your store

Call this when variants or whole products are deleted in your store. OptiWMS keeps its products and their stock (the goods are still on the shelves) and stops sending stock levels for them. Ids OptiWMS has never seen are ignored, so sending a delete twice is safe. Send `variantIds`, `productIds` or both, up to 250 of each. A product id removes every variant of that product.

Request body

FieldTypeRequiredDescription
variantIdsarrayno
productIdsarrayno
Request example: One variant and one whole product
{
  "variantIds": [
    "302"
  ],
  "productIds": [
    "250"
  ]
}
Request example: No ids at all
{}

Response 200

Done. `removed` counts the variants OptiWMS knew and has now marked removed.

FieldTypeRequiredDescription
removedintegeryesThe variants OptiWMS knew and has now marked removed.
Response 200: removeVariants
{
  "removed": 2
}

Response 400

The body is not valid. Nothing was changed; fix the fields listed in `details` and send again.

FieldTypeRequiredDescription
errorobjectyes
Response 400: invalid
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Some fields of the request body are missing or not valid. Fix the fields listed in details and send the call again.",
    "details": [
      {
        "path": "variantIds",
        "message": "Send at least one id in variantIds or productIds."
      }
    ],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 413

The body is over 1 MiB. A request that declares its length gets this small JSON body and no request id; one sent without a length gets an empty body. Send fewer variants per call.

FieldTypeRequiredDescription
errorstringyes
Response 413: tooLarge
{
  "error": "request body too large: this endpoint accepts at most 1 MiB"
}

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Read an order

What OptiWMS holds for an order this integration sent, and where it is. Use it to recover after a lost answer (did the order arrive, did it ship) and to learn when a released order is `pending` again. `status` is one of `pending` (waiting for the warehouse), `picking`, `packing`, `partially_shipped`, `shipped` or `canceled`. `shipments` lists every parcel that has left, with its tracking and the lines it carried. It reads only orders this integration sent; an order another integration sent answers 404.

Parameters

NameInRequiredDescription
orderIdpathyesYour store's id for the order, percent-encoded in the path.

Response 200

The order and where it is.

FieldTypeRequiredDescription
orderIdstringyesAn id as your store writes it.
numberstringyes
statusstring (pending, picking, packing, shipped, partially_shipped, canceled)yes
shipmentsarrayyesShipments OptiWMS has sent. A set line's quantity is the number of sets in that shipment.
updatedAtstringyesAn ISO 8601 time with its offset.
Response 200: pending
{
  "orderId": "5001",
  "number": "#1001",
  "status": "pending",
  "shipments": [],
  "updatedAt": "2026-10-27T09:30:12+11:00"
}
Response 200: picking
{
  "orderId": "5001",
  "number": "#1001",
  "status": "picking",
  "shipments": [],
  "updatedAt": "2026-10-27T10:02:40+11:00"
}
Response 200: shipped
{
  "orderId": "5001",
  "number": "#1001",
  "status": "shipped",
  "shipments": [
    {
      "shipmentId": "8812",
      "carrier": "Australia Post",
      "trackingNumber": "33ABC1234567",
      "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC1234567",
      "shippedAt": "2026-10-27T15:20:00+11:00",
      "lines": [
        {
          "lineId": "11",
          "quantity": 2
        },
        {
          "lineId": "12",
          "quantity": 1
        }
      ]
    }
  ],
  "updatedAt": "2026-10-27T15:20:00+11:00"
}
Response 200: One parcel has left, the rest is still in the warehouse
{
  "orderId": "5001",
  "number": "#1001",
  "status": "partially_shipped",
  "shipments": [
    {
      "shipmentId": "8812",
      "carrier": "Australia Post",
      "trackingNumber": "33ABC1234567",
      "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC1234567",
      "shippedAt": "2026-10-27T15:20:00+11:00",
      "lines": [
        {
          "lineId": "11",
          "quantity": 2
        }
      ]
    }
  ],
  "updatedAt": "2026-10-27T15:20:00+11:00"
}
Response 200: canceled
{
  "orderId": "5001",
  "number": "#1001",
  "status": "canceled",
  "shipments": [],
  "updatedAt": "2026-10-27T11:00:00+11:00"
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 404

OptiWMS holds no order with this id from this integration (`ORDER_NOT_FOUND`). If you expected it, send it with `PUT /orders/{orderId}`; it is safe to send an order again.

FieldTypeRequiredDescription
errorobjectyes
Response 404: unknown
{
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "OptiWMS holds no order #9999 from this store. Send it with PUT orders/{orderId} if it should ship.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Send an order

Send an order once it is paid and ready to ship, and again whenever it changes. The body is always the whole current order, never a change list. Send only orders that are ready to ship: an order your store has not been paid for is simply not sent yet. What OptiWMS does with the body: - A new order is imported: 201 `created`. - The same as the last version you sent: 200 `unchanged`. Only what the warehouse acts on counts as a change: the lines (id, SKU or variantId, quantity), the delivery address, the customer's name, email and phone, and the shipping method's code and name. A different note, price, total or `updatedAt` alone answers `unchanged` and leaves the order as it is. The comparison is with the last version you sent, not with the order as OptiWMS holds it. - Older than the version OptiWMS has applied (its `updatedAt` is earlier): 200 `stale`, nothing changes. - Changed, and the warehouse has not started on it: applied (lines rebuilt, address and customer refreshed). 200 `updated`. - Changed, and the warehouse has started on it (the order is in a pick, wave or pack, has a shipping label, or has been handed to a shipping platform): 409 `ORDER_IN_FULFILLMENT`. Nothing on the order changes, and OptiWMS raises an Order edited task naming what your store wanted changed, so the warehouse sees it even if your code only logs the 409. When staff cancel the pick, `GET /orders/{orderId}` shows `pending` again; then send the order again and it applies as an update. - Changed, and shipped: 409 `ORDER_SHIPPED`. Changed, and canceled in OptiWMS: 409 `ORDER_CANCELED`. - Canceled with `POST /orders/{orderId}/cancel` before OptiWMS ever saw it: not imported, 200 `canceled`. Lines whose SKU OptiWMS does not know are imported as held lines and raised as product questions in the dashboard; the rest of the order goes ahead. An order carries at most 250 lines.

Parameters

NameInRequiredDescription
orderIdpathyesYour store's id for the order, as your store writes it. Percent-encode it in the path (`#1001` travels as `%231001`) and sign the encoded path.

Request body

FieldTypeRequiredDescription
numberstringyesThe order number your staff and customers see, such as `#1001`.
createdAtstringnoAn ISO 8601 time with its offset.
updatedAtstringnoAn ISO 8601 time with its offset.
currencystringyesThe ISO 4217 code of every amount in the order, such as `AUD`.
customerobjectno
shipToobjectno
shippingobjectnoThe shipping method the customer chose. OptiWMS maps `code` and `name` to a carrier service in the dashboard's shipping settings.
totalsobjectno
notesstringnoThe customer's note, shown to staff.
linesarrayyes
Request example: A new order
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T09:30:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "99.85",
    "shipping": "12.00",
    "tax": "10.17",
    "total": "111.85",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 2,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: Only the note and a price changed
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T10:15:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "94.85",
    "shipping": "12.00",
    "tax": "9.71",
    "total": "106.85",
    "taxesIncluded": true
  },
  "notes": "Please ring the bell",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 2,
      "unitPrice": "27.45"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: An older version arriving late
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T09:00:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "69.90",
    "shipping": "12.00",
    "tax": "7.45",
    "total": "81.90",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 1,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: The customer added a third tee before picking started
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T10:15:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "129.80",
    "shipping": "12.00",
    "tax": "12.89",
    "total": "141.80",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 3,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: No order number, a lowercase currency and a line with no quantity
{
  "currency": "aud",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]"
  },
  "lines": [
    {
      "id": "11",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 0,
      "unitPrice": "29.95"
    }
  ]
}
Request example: A third tee added after picking started
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T10:15:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "129.80",
    "shipping": "12.00",
    "tax": "12.89",
    "total": "141.80",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 3,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: A new address after the order shipped
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-28T08:00:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 3",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "99.85",
    "shipping": "12.00",
    "tax": "10.17",
    "total": "111.85",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 2,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}
Request example: A change to an order already canceled in OptiWMS
{
  "number": "#1001",
  "createdAt": "2026-10-27T09:30:00+11:00",
  "updatedAt": "2026-10-27T10:15:00+11:00",
  "currency": "AUD",
  "customer": {
    "name": "Jo Citizen",
    "email": "[email protected]",
    "phone": "+61 400 000 000"
  },
  "shipTo": {
    "name": "Jo Citizen",
    "phone": "+61 400 000 000",
    "company": "",
    "line1": "1 George St",
    "line2": "Level 2",
    "city": "Sydney",
    "provinceCode": "NSW",
    "postalCode": "2000",
    "countryCode": "AU"
  },
  "shipping": {
    "code": "express",
    "name": "Express Post",
    "price": "12.00"
  },
  "totals": {
    "subtotal": "129.80",
    "shipping": "12.00",
    "tax": "12.89",
    "total": "141.80",
    "taxesIncluded": true
  },
  "notes": "Leave at the door",
  "lines": [
    {
      "id": "11",
      "productId": "201",
      "variantId": "301",
      "sku": "TEE-BLK-M",
      "title": "Organic tee - Black / M",
      "quantity": 3,
      "unitPrice": "29.95"
    },
    {
      "id": "12",
      "sku": "CAP-RED",
      "title": "Cap",
      "quantity": 1,
      "unitPrice": "39.95"
    }
  ]
}

Response 200

The order was known. `status` says what happened: `unchanged` (nothing the warehouse acts on changed), `stale` (older than the version applied), `updated` (the change was applied) or `canceled` (canceled before OptiWMS saw it; not imported).

FieldTypeRequiredDescription
statusstring (created, unchanged, stale, updated, canceled)yes
Response 200: unchanged
{
  "status": "unchanged"
}
Response 200: stale
{
  "status": "stale"
}
Response 200: updated
{
  "status": "updated"
}
Response 200: The store canceled the order before sending it
{
  "status": "canceled"
}

Response 201

The order was imported.

FieldTypeRequiredDescription
statusstring (created, unchanged, stale, updated, canceled)yes
Response 201: created
{
  "status": "created"
}

Response 400

The body is not valid. Nothing was imported or changed; fix the fields listed in `details` and send the order again.

FieldTypeRequiredDescription
errorobjectyes
Response 400: invalid
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Some fields of the request body are missing or not valid. Fix the fields listed in details and send the call again.",
    "details": [
      {
        "path": "number",
        "message": "Required: the order number your staff and customers see, such as #1001."
      },
      {
        "path": "currency",
        "message": "Required: the three-letter ISO 4217 currency code in capitals, such as AUD."
      },
      {
        "path": "lines[0].quantity",
        "message": "Send a quantity of at least 1; leave out a line with nothing to ship."
      }
    ],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 409

The order changed, but OptiWMS cannot apply the change. Nothing on the order changed. - `ORDER_IN_FULFILLMENT`: the warehouse has started on the order. OptiWMS has raised an Order edited task for staff. Once they cancel the pick, `GET /orders/{orderId}` shows `pending`; send the order again then. - `ORDER_SHIPPED`: the order has shipped. Arrange any change with the customer outside OptiWMS. - `ORDER_CANCELED`: the order is canceled in OptiWMS and will not ship; OptiWMS has sent your store the orderCancel call. Treat it as canceled, and contact the warehouse if it should ship after all.

FieldTypeRequiredDescription
errorobjectyes
Response 409: inFulfillment
{
  "error": {
    "code": "ORDER_IN_FULFILLMENT",
    "message": "The warehouse has started on this order, so the change cannot apply yet. An Order edited task now tells the warehouse what your store wanted changed (the lines); when staff release the order (GET /orders/{orderId} shows pending), send it again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}
Response 409: shipped
{
  "error": {
    "code": "ORDER_SHIPPED",
    "message": "This order has shipped, so the change to its delivery address was not applied. Arrange the change with the customer directly.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}
Response 409: orderCanceled
{
  "error": {
    "code": "ORDER_CANCELED",
    "message": "This order is canceled in OptiWMS and will not ship, so the change was not applied. Treat it as canceled in your store, and contact the warehouse if it should ship after all.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 413

The body is over 1 MiB. A request that declares its length gets this small JSON body and no request id; one sent without a length gets an empty body. Send fewer variants per call.

FieldTypeRequiredDescription
errorstringyes
Response 413: tooLarge
{
  "error": "request body too large: this endpoint accepts at most 1 MiB"
}

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Cancel an order

Cancels the order in OptiWMS, under the same rule as a change: - Not started in the warehouse: canceled, 200 `canceled`. - Already canceled: 200 `unchanged`. - Never sent to OptiWMS: the cancel is recorded, so the order is not imported if it arrives later. 200 `canceled`. - Started in the warehouse: 409 `ORDER_IN_FULFILLMENT`. Ask the warehouse to cancel the pick, then send the cancel again. - Shipped: 409 `ORDER_SHIPPED`. The body is optional. `reason` is kept with the cancel record of an order OptiWMS has not received yet, so staff see why a later create was not imported; for an order OptiWMS holds, the cancel itself is what staff see and the reason is not stored.

Parameters

NameInRequiredDescription
orderIdpathyesYour store's id for the order, as your store writes it. Percent-encode it in the path (`#1001` travels as `%231001`) and sign the encoded path.

Request body

FieldTypeRequiredDescription
reasonstringnoWhy the order was canceled, shown to staff.
Request example: An order the warehouse has not started on
{
  "reason": "Customer asked to cancel"
}
Request example: An order OptiWMS has not received yet
{
  "reason": "Payment reversed"
}
Request example: The same cancel sent twice
{
  "reason": "Customer asked to cancel"
}
Request example: An order being picked
{
  "reason": "Customer asked to cancel"
}
Request example: An order that has shipped
{
  "reason": "Customer asked to cancel"
}
Request example: A reason that is not text
{
  "reason": 42
}

Response 200

`canceled`: the order is canceled now (or will not be imported). `unchanged`: it was already canceled.

FieldTypeRequiredDescription
statusstring (canceled, unchanged)yes
Response 200: canceled
{
  "status": "canceled"
}
Response 200: canceledBeforeImport
{
  "status": "canceled"
}
Response 200: alreadyCanceled
{
  "status": "unchanged"
}

Response 400

The body is not valid. Nothing was canceled; fix the fields listed in `details` and send again.

FieldTypeRequiredDescription
errorobjectyes
Response 400: invalid
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Some fields of the request body are missing or not valid. Fix the fields listed in details and send the call again.",
    "details": [
      {
        "path": "reason",
        "message": "Send the reason as text, or leave it out."
      }
    ],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 401

The integration ID is unknown, or the signature or timestamp did not verify. The body is empty on purpose. Check the integration ID, the secret, the full path you signed (encoded as sent), the exact body bytes and your clock; the signature checker on the integration page shows which.

Response 409

The order cannot be canceled in OptiWMS. Nothing changed. - `ORDER_IN_FULFILLMENT`: the warehouse has started on the order. Ask the warehouse to cancel the pick in OptiWMS, then send the cancel again. - `ORDER_SHIPPED`: the order has shipped; handle it as a return.

FieldTypeRequiredDescription
errorobjectyes
Response 409: inFulfillment
{
  "error": {
    "code": "ORDER_IN_FULFILLMENT",
    "message": "The warehouse has started on this order, so it was not canceled. Ask the warehouse to cancel the pick in OptiWMS, then send the cancel again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}
Response 409: shipped
{
  "error": {
    "code": "ORDER_SHIPPED",
    "message": "This order has shipped, so it was not canceled. Handle it as a return.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 410

The integration is disabled or archived. The body is empty. Stop sending; an administrator can turn it back on in the dashboard.

Response 413

The body is over 1 MiB. A request that declares its length gets this small JSON body and no request id; one sent without a length gets an empty body. Send fewer variants per call.

FieldTypeRequiredDescription
errorstringyes
Response 413: tooLarge
{
  "error": "request body too large: this endpoint accepts at most 1 MiB"
}

Response 429

More than 300 calls in a minute for this integration. Wait the seconds in `Retry-After`, then send again.

FieldTypeRequiredDescription
errorobjectyes
Response 429: rateLimited
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "This store has sent more than 300 calls in a minute. Wait 42 seconds (the Retry-After header), then send the call again.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Response 500

A problem on OptiWMS's side; nothing was changed. Send the call again in a minute; if it keeps failing, contact support with the request id.

FieldTypeRequiredDescription
errorobjectyes
Response 500: internal
{
  "error": {
    "code": "INTERNAL",
    "message": "OptiWMS could not complete this call because of a problem on its side. Send the call again in a minute; if it keeps failing, contact OptiWMS support with this request id.",
    "details": [],
    "requestId": "3f6c2b1e-8d4a-4c1f-9b7e-2a5d6e8f9a10"
  }
}

Calls OptiWMS makes to your store

POSTs to your registered base URL, signed the same way over the path your server receives.

POST

stock

OptiWMS calls POST {your base URL}/stock: a stock level changed

Sent whenever the stock OptiWMS can sell for a variant changes, after the integration's buffer stock. `available` is the absolute figure to set, never a change, so a late or repeated call is harmless. Calls are retried with backoff until your endpoint accepts them, and a newer level replaces one still waiting. Find the variant by `variantId` when the call carries one: it is the `variantId` you sent with `PUT /products`. A variant you sent without a SKU gets a SKU OptiWMS makes up, which your store does not know, so `sku` alone cannot find it. `variantId` is absent only for a product that never came from `PUT /products`. Answer any 2xx within 10 seconds to accept, with a JSON body (`{}` is enough) or with 204 and no body. A 200 with an empty body counts as no answer. Any other answer, or no answer in time, is retried. A 2xx whose body is the error envelope (`{"error": {"code", "message"}}`, optionally with `retryAfterSeconds`) counts as a failure.

Request body

FieldTypeRequiredDescription
skustringyesThe variant's SKU. For a variant you sent without one, a SKU OptiWMS made up; find the variant by `variantId` instead.
variantIdstringnoAn id as your store writes it.
availableintegeryesThe units you may sell, after the integration's buffer stock. Set your store's count to this figure.
locationCodestringnoYour store's code for the location the level is for, when the integration maps an OptiWMS warehouse to one of your store's locations. Absent otherwise.
Example: stock
{
  "sku": "TEE-BLK-M",
  "variantId": "40001",
  "available": 42
}

OptiWMS calls POST {your base URL}/shipments: a shipment left the warehouse

Sent when a shipment of an order leaves the warehouse, listing the lines (by your line id) and quantities it carried, so an order shipped in several shipments sends one call per shipment. `shipmentId` is OptiWMS's id for the shipment; keep it, because a later tracking change names it. A shipment that went out in several parcels is one call: `trackingNumber` is the first parcel's and `moreTracking` lists the others. Rarely, units packed after the first call (a remainder) are sent in a second call with the same `shipmentId`, listing only the units it adds. Every call carries `X-Idempotency-Key`, the same on each retry of that call and different for the next one, so use it, not `shipmentId`, to recognize a call you already applied. An order that ships short and is closed with units left over is not a store call: the shipments list only what shipped, and the `order.short_shipped` notification webhook tells your system the rest will not ship. Answer any 2xx within 10 seconds to accept, with a JSON body (`{}` is enough) or with 204 and no body; anything else is retried. When your store keeps its own id for the shipment, answer with it as `shipmentId`, and later tracking calls name your id instead of OptiWMS's.

Request body

FieldTypeRequiredDescription
orderIdstringyesAn id as your store writes it.
shipmentIdstringyesAn id as your store writes it.
trackingNumberstringyesThe first parcel's tracking number. Empty when the shipment left without tracking.
trackingUrlstringyesThe first parcel's tracking page. Empty when there is none.
carrierstringyesThe carrier's name. Empty when the shipment left without tracking.
shippedAtstringyesAn ISO 8601 time with its offset.
linesarrayyes
moreTrackingarraynoThe other parcels of a shipment that went out in several, each tracking number once. Absent for a single parcel.
Example: shipment
{
  "orderId": "5001",
  "shipmentId": "8812",
  "trackingNumber": "33ABC1234567",
  "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC1234567",
  "carrier": "Australia Post",
  "shippedAt": "2026-10-27T15:20:00+11:00",
  "lines": [
    {
      "lineId": "11",
      "quantity": 2
    },
    {
      "lineId": "12",
      "quantity": 1
    }
  ]
}
Example: twoParcels
{
  "orderId": "5002",
  "shipmentId": "8813",
  "trackingNumber": "33ABC2000001",
  "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC2000001",
  "carrier": "Australia Post",
  "shippedAt": "2026-10-27T15:40:00+11:00",
  "lines": [
    {
      "lineId": "21",
      "quantity": 6
    }
  ],
  "moreTracking": [
    {
      "trackingNumber": "33ABC2000002",
      "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC2000002"
    }
  ]
}

OptiWMS calls POST {your base URL}/shipments/{shipmentId}/tracking: a parcel's tracking changed

Sent when the label of a parcel already reported is replaced and its tracking changes, or when its tracking link arrives after the number. `{shipmentId}` in the path is the id from the earlier shipments call (yours, when you answered with one), percent-encoded. `previousTrackingNumber` is the number this one replaces, so a shipment of several parcels changes the right one; replace that number in your store, or the shipment's only number when it is absent. Answer any 2xx within 10 seconds to accept, with a JSON body (`{}` is enough) or with 204 and no body; anything else is retried.

Request body

FieldTypeRequiredDescription
trackingNumberstringyesThe parcel's tracking number now.
trackingUrlstringyesIts tracking page. Empty when there is none.
carrierstringyes
previousTrackingNumberstringnoThe number this one replaces; the same number when only the tracking page arrived. Absent when OptiWMS does not know it.
Example: tracking
{
  "trackingNumber": "33ABC7654321",
  "trackingUrl": "https://auspost.com.au/mypost/track/details/33ABC7654321",
  "carrier": "Australia Post",
  "previousTrackingNumber": "33ABC1234567"
}

OptiWMS calls POST {your base URL}/orders/{orderId}/cancel: the warehouse canceled an order

Sent when staff cancel an order in OptiWMS. The warehouse will not ship it: treat the order as canceled in your store. `{orderId}` in the path is your store's order id, percent-encoded. Answer 2xx for an order your store already shows as canceled too. Answer any 2xx within 10 seconds to accept, with a JSON body (`{}` is enough) or with 204 and no body; anything else is retried.

Request body

FieldTypeRequiredDescription
reasonstringnoWhy the warehouse canceled the order.
Example: cancel
{
  "reason": "Canceled in OptiWMS by the warehouse"
}

Connect your own store

Register the integration in the dashboard, then start with GET /ping.