Skip to Content
Welcome to Zendera Knowledge Hub
For DevelopersProduct Upsert

Product Upsert API

v1

Create, 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/upsert

Products 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_HERE

Base 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:

FieldTypeDescription
externalOrderNumberstringThe identifier for the order being changed. Required on every entry.
productRequestobjectThe 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_STATUS
  • NOT_READY_ORDER_PRODUCT_LOCATION_STATUS
  • PENDING_ORDER_PRODUCT_ORDER_LOCATION_STATUS
  • COMPLETE_ORDER_PRODUCT_ORDER_LOCATION_STATUS
  • REMOVED_ORDER_PRODUCT_ORDER_LOCATION_STATUS

Product Fields

The productRequest object supports the following fields:

FieldTypeNotes
namestringProduct name.
externalIdstringYour unique product identifier.
descriptionstringOptional description.
quantityintegerNumber of items.
weightfloatWeight in kg.
length, width, heightfloatDimensions in cm.
barcodeIdstringBarcode for scanning.
barcodeTypestringBarcode symbology (e.g. CODE128, EAN13).
skillsarrayRequired/prohibited skills (see Skills).
productCategoryobject{ id, name, externalId }.
pickupWarehouseLocationstringPickup warehouse location.
deliveryWarehouseLocationstringDelivery warehouse location.
orderProductLocationStatusPickupenumStatus at pickup (see Product Status Control).
orderProductLocationStatusDeliveryenumStatus at delivery.
customUnitsarrayCustom units (see Skills & Units).
parentExternalIdstringParent product’s externalId, for hierarchy.
atomTypestringE.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_CODE
  • ORDER_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_CODEexternalId missing.
  • MISSING_EXTERNAL_ORDER_NUMBER_CODEexternalOrderNumber missing.
  • 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_PRODUCT for automated systems.
  • Use IGNORE_IF_MODIFIED when manual changes should be preserved.
Last updated on