Product Upsert API
v1Create, update, delete, and upsert products on existing orders in a single call, with support for hierarchical (parent/child) relationships.
Where this fits in your operation
Orders rarely ship exactly as ordered — this endpoint keeps the order’s product lines matching reality:
- After picking in your warehouse: the customer ordered 10 crates but 8 were in stock — update the quantities on the existing order before dispatch.
- Add a line after the fact: a forgotten item joins tomorrow’s existing order instead of creating a second delivery.
Interactive API Explorer
Loading API Documentation...
Endpoints Overview
POST /v1/order-products/upsertProducts are the line items on an order — the things being moved. This endpoint changes what products exist on an order (typical use: your WMS confirming what was actually picked and packed). To change how products relate to each other, see the Atoms API.
A single request can combine up to four operation arrays — create, update,
delete, and upsert — plus an updateConfig object controlling overall
behaviour.
Authentication
Authorization: apikey YOUR_API_KEY_HEREBase URLs
- Production:
https://app.zenderatms.com/api/ - Staging:
https://staging.zenderatms.com/api/
Request Structure
Every entry in the create, update, delete, and upsert arrays uses the
same wrapper shape: an externalOrderNumber identifying the order, plus a
productRequest holding the product fields.
{
"create": [
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "New Pallet",
"externalId": "PALLET_NEW_001",
"quantity": 1,
"weight": 45.0,
"atomType": "colli",
"barcodeId": "NEW_PALLET_BARCODE"
}
},
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "New Product",
"externalId": "PROD_NEW_001",
"quantity": 8,
"weight": 2.5,
"parentExternalId": "PALLET_NEW_001",
"atomType": "trade item"
}
}
],
"update": [
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"externalId": "PROD_001",
"quantity": 12
}
}
],
"delete": [
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"externalId": "PROD_OLD_001"
}
}
],
"upsert": [
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "Flexible Product",
"externalId": "PROD_FLEX_001",
"quantity": 5,
"weight": 10.0
}
}
],
"updateConfig": {
"orderProducts": "REPLACE_EXISTING_ORDER_PRODUCT",
"hierarchicalMode": true
}
}Each array entry is a UpsertProductRequest:
| Field | Type | Description |
|---|---|---|
externalOrderNumber | string | The identifier for the order being changed. Required on every entry. |
productRequest | object | The product fields (see Product Fields). |
Operations
Create
Adds new products to the order. Each entry is a fresh product spec.
Update
Changes existing products. Only the fields you send are updated.
Upsert
“Update if it exists, create if not.” Matched by the product’s externalId.
Delete
Removes products from the order. Each delete entry uses the same
{ externalOrderNumber, productRequest } wrapper — identify the product to
remove via the productRequest (for example by externalId).
Update Configuration
The updateConfig object controls how operations are applied.
Product Update Behavior
orderProducts controls how existing products are updated. Values come from the
ProductUpdateBehaviorType enum:
"UNKNOWN_UPDATE_PRODUCT_BEHAVIOR"(default): Use system default behaviour."REPLACE_EXISTING_ORDER_PRODUCT": Replace existing product data with the new values."IGNORE_IF_MODIFIED": Skip the update if the product has been manually modified in the system.
When to use each option:
- REPLACE_EXISTING_ORDER_PRODUCT: Use when your system data should take precedence — typical for fully automated sync.
- IGNORE_IF_MODIFIED: Use when manual changes made by dispatchers/operators should be preserved.
Hierarchical Mode
hierarchicalMode is a boolean that enables handling of parent/child product
relationships (atoms).
When hierarchicalMode: true:
- Create: Unaffected — proceeds as usual.
- Update: Updates the parent product and creates/updates any child products, but returns an error if the parent is missing.
- Upsert: Follows the update behaviour, and also creates the parent product if it is missing.
- Delete: Removes the product and all of its associated child products.
When hierarchicalMode: false (default):
- Operations work independently on each product.
- Parent/child relationships are not automatically managed.
Combined Configuration Example
{
"updateConfig": {
"orderProducts": "IGNORE_IF_MODIFIED",
"hierarchicalMode": true
}
}This configuration will:
- Skip updates to products that have been manually modified.
- Automatically manage parent/child relationships.
- Delete entire product trees when a parent is deleted.
Product Status Control
Set product status at specific locations using the OrderProductOrderLocationStatus
enum:
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"externalId": "PROD_001",
"orderProductLocationStatusPickup": "COMPLETE_ORDER_PRODUCT_ORDER_LOCATION_STATUS",
"orderProductLocationStatusDelivery": "PENDING_ORDER_PRODUCT_ORDER_LOCATION_STATUS"
}
}Available status values:
UNKNOWN_ORDER_PRODUCT_LOCATION_STATUSNOT_READY_ORDER_PRODUCT_LOCATION_STATUSPENDING_ORDER_PRODUCT_ORDER_LOCATION_STATUSCOMPLETE_ORDER_PRODUCT_ORDER_LOCATION_STATUSREMOVED_ORDER_PRODUCT_ORDER_LOCATION_STATUS
Product Fields
The productRequest object supports the following fields:
| Field | Type | Notes |
|---|---|---|
name | string | Product name. |
externalId | string | Your unique product identifier. |
description | string | Optional description. |
quantity | integer | Number of items. |
weight | float | Weight in kg. |
length, width, height | float | Dimensions in cm. |
barcodeId | string | Barcode for scanning. |
barcodeType | string | Barcode symbology (e.g. CODE128, EAN13). |
skills | array | Required/prohibited skills (see Skills). |
productCategory | object | { id, name, externalId }. |
pickupWarehouseLocation | string | Pickup warehouse location. |
deliveryWarehouseLocation | string | Delivery warehouse location. |
orderProductLocationStatusPickup | enum | Status at pickup (see Product Status Control). |
orderProductLocationStatusDelivery | enum | Status at delivery. |
customUnits | array | Custom units (see Skills & Units). |
parentExternalId | string | Parent product’s externalId, for hierarchy. |
atomType | string | E.g. "colli" or "trade item". |
Skills and Units
skills entries describe handling requirements:
{
"skills": [
{
"identifier": { "id": 12, "name": "Forklift" },
"skillType": "REQUIRED"
}
]
}skillType is one of UNKNOWN, REQUIRED, or PROHIBITED.
customUnits entries attach measurement units:
{
"customUnits": [
{
"identifier": { "id": 1, "name": "Pallet" },
"quantity": 1
}
]
}Hierarchical Product Example
Creating a complete pallet structure:
{
"upsert": [
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "EUR Pallet",
"externalId": "PALLET_001",
"atomType": "colli",
"quantity": 1,
"weight": 25.0,
"length": 120.0,
"width": 80.0,
"height": 180.0
}
},
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "Product A",
"externalId": "PROD_A_001",
"parentExternalId": "PALLET_001",
"atomType": "trade item",
"quantity": 10,
"weight": 20.0
}
},
{
"externalOrderNumber": "ORDER_123",
"productRequest": {
"name": "Product B",
"externalId": "PROD_B_001",
"parentExternalId": "PALLET_001",
"atomType": "trade item",
"quantity": 5,
"weight": 15.0
}
}
],
"updateConfig": {
"orderProducts": "REPLACE_EXISTING_ORDER_PRODUCT",
"hierarchicalMode": true
}
}Response Structure
The response groups results by operation into four arrays — createResults,
updateResults, deleteResults, and upsertResults. Each result is a oneof:
exactly one of the success types (productCreated, productUpdated,
productDeleted) or, for draft orders, the draft variants (productDraftCreated,
productDraftUpdated, productDraftDeleted); or a failure type (failedCreate,
failedUpdate, failedDelete, failedUpsert).
{
"createResults": [
{
"productCreated": {
"OrderProductID": 1001,
"externalOrderId": "ORDER_123",
"externalProductId": "PALLET_NEW_001"
}
}
],
"updateResults": [
{
"productUpdated": {
"OrderProductID": 1002,
"externalOrderId": "ORDER_123",
"externalProductId": "PROD_001"
}
}
],
"deleteResults": [
{
"productDeleted": {
"OrderProductID": 1000,
"externalOrderId": "ORDER_123",
"externalProductId": "PROD_OLD_001"
}
}
],
"upsertResults": [
{
"failedUpsert": {
"externalOrderId": "ORDER_123",
"externalProductId": "INVALID_PROD_001",
"reasons": [{ "code": "INVALID_PRODUCT_CODE" }]
}
}
]
}For draft orders, the success types are productDraftCreated /
productDraftUpdated / productDraftDeleted (carrying OrderProductDraftID)
instead.
A failure result carries externalOrderId, externalProductId, and an array of
reasons, each with a code from the
error codes below.
Error Handling
Failures are reported per result via the reasons array, using codes from the
InvalidInputCode enum:
UNKNOWN_INVALID_INPUT_CODEORDER_PRODUCT_NOT_FOUND_CODE— product to update/delete doesn’t exist.ORDER_PRUDUCT_ALREADY_EXISTS_CODE— product already exists (create operation). (Spelling matches the API enum.)INVALID_PRODUCT_CODE— product validation failed.INVALID_PRODUCT_SKILL_CODE— invalid skill on the product.ORDER_NOT_FOUND_CODE— order not found.PICKUP_NOT_FOUND_CODE— pickup not found.DELIVERY_NOT_FOUND_CODE— delivery not found.MISSING_EXTERNAL_PRODUCT_NUMBER_CODE—externalIdmissing.MISSING_EXTERNAL_ORDER_NUMBER_CODE—externalOrderNumbermissing.MISSING_PRODUCT_IDENTIFIER_CODE— no usable product identifier supplied.
Best Practices
1. Use Hierarchical Mode for Complex Structures
When working with colli and trade items, enable hierarchicalMode so
parent/child relationships are handled automatically.
2. Batch Operations
Combine multiple operations in a single request for better performance:
3. Handle Partial Failures
The response carries both successes and failures per operation. Always inspect each result array for failure types:
4. Use Appropriate Update Behavior
Choose the right orderProducts behaviour based on your workflow:
- Use
REPLACE_EXISTING_ORDER_PRODUCTfor automated systems. - Use
IGNORE_IF_MODIFIEDwhen manual changes should be preserved.