Skip to Content
Welcome to Zendera Knowledge Hub
For DevelopersOrders Import

Orders Import API

v3

The single endpoint for creating, updating, and merging orders in Zendera. Supports bulk and single-order imports, hierarchical products, and per-area update control.

Where this fits in your operation

This is the front door: every order your systems create in Zendera comes through this endpoint.

  • Order confirmation in your ERP → push the order to Zendera immediately, or batch the day’s confirmed orders in one import call.
  • Webshop checkout → import the order with the customer’s chosen address; if you let the customer pick a delivery slot, follow up with booking on an interval.
  • Time-window control: send earliest/latest when your system owns the window for that order, or omit them and let dispatchers govern windows from the location setup in Zendera — see time windows.

Interactive API Explorer

Loading API Documentation...

Endpoints Overview

The integration API has a single import endpoint for creating orders, used for both bulk and single-order flows:

POST /v3/orders/import

The same call also handles updates and merges for orders that already exist. Re-importing an order with the same externalId triggers the update path automatically — there is no separate “update order” call.

There is no v1 or v2 import endpoint exposed to integrators. POST /v3/orders/import is the only way to create orders.

Authentication

Authorization: apikey YOUR_API_KEY_HERE

Base URLs

  • Production: https://app.zenderatms.com/api/
  • Staging: https://staging.zenderatms.com/api/

Key Features

  • Batch import — import multiple orders in a single request
  • Flexible identifiers — use external IDs, names, or internal Zendera IDs for lookup
  • Update control — configure per-area how existing orders, locations, products, groups, and pricing are handled
  • Validation — comprehensive error reporting; failures often produce drafts rather than rejections
  • ERP integration — built-in support for ERP system integration and order merging
  • Hierarchical products — support for atoms (colli / trade items) with parent-child relationships

Request Structure

The request body is an ImportOrdersRequest containing one or more ImportOrderRequest orders plus optional fallbacks and update configuration.

{ "orders": [ { "externalId": "ORDER_123", "date": "2024-01-15", "reference": "Customer Reference", "customer": { "id": 123, "businessName": "Customer Name", "externalId": "CUST_001" }, "vehicleType": { "id": 5, "name": "Van" }, "orderType": { "id": 2, "name": "Delivery" }, "pickup": { "name": "Warehouse", "externalId": "LOC-warehouse-1", "address": { "address1": "Main Street 123", "city": "Oslo", "postalCode": "0123", "country": "Norway" }, "earliest": "2024-01-15T08:00:00Z", "latest": "2024-01-15T10:00:00Z", "estimatedTimeOnLocation": 15, "sendNotificationBeforeArriving": true, "contacts": [ { "firstName": "John", "lastName": "Doe", "phoneNumber": "+4712345678", "emailAddress": "john@example.com" } ] }, "delivery": { "name": "Customer Location", "externalId": "LOC-cust-99", "address": { "address1": "Customer Street 456", "city": "Bergen", "postalCode": "5000", "country": "Norway" }, "earliest": "2024-01-15T14:00:00Z", "latest": "2024-01-15T16:00:00Z" }, "products": [ { "name": "Pallet", "quantity": 1, "weight": 50.0, "length": 120.0, "width": 80.0, "height": 180.0, "barcodeId": "PALLET_001", "externalId": "PALLET_001", "atomType": "colli" }, { "name": "Product A", "quantity": 10, "weight": 2.0, "externalId": "PROD_A_001", "parentExternalId": "PALLET_001", "atomType": "trade item" } ], "groups": [], "pricing": [] } ], "fallbackCustomer": { "id": 1, "businessName": "Default Customer" }, "updateConfig": { "orderLocation": "REPLACE_EXISTING_ORDER_LOCATION_BEHAVIOR", "orderProducts": "REPLACE_EXISTING_ORDER_PRODUCT", "order": "REPLACE_EXISTING_ORDER_BEHAVIOR", "allowMerge": true } }

Top-level request fields

FieldNotes
ordersOne or more orders. Each item should include pickup, delivery, products, customer, vehicleType, orderType, and date, plus optional groups and pricing arrays.
fallbackCustomerOptional. Used for any order that does not specify its own customer.
fallbackVehicleTypeOptional. Used for any order that does not specify its own vehicleType.
fallbackOrderTypeOptional. Used for any order that does not specify its own orderType.
updateConfigControls how Zendera reacts when an order or a sub-entity already exists. See Update configuration.

Choosing your externalId values

externalId is your idempotency key. Reuse it consistently and re-imports become safe — Zendera updates the existing order instead of erroring.

Use a stable, immutable identifier from your source system — not a name, not something users can rename, not something that changes when records are merged. Good choices:

  • A system-generated GUID or UUID.
  • An immutable customer or location number from your master data.
  • A primary-key ID you guarantee never to recycle.

If externalId changes, Zendera treats the next import as a new entity rather than an update to the existing one.

If your source system separates billing entities from ship-to entities, map them to Zendera’s customer and location respectively — one Zendera customer can have many locations linked to it via orders.

Atom Types Explained

Atom types define the nature of products in hierarchical structures, set via the product’s atomType field:

"colli" — Container Items

  • Used for grouping multiple trade items
  • Examples: pallets, boxes, parcels
  • Can have child products
  • Typically scanned as a unit

"trade item" — Individual Products

  • End products that customers purchase
  • Can be part of a colli
  • Usually the leaf nodes in hierarchies

Hierarchical Structure Example

Pallet (colli) - PALLET_001 ├── Product A (trade item) - PROD_A_001 (qty: 10) └── Product B (trade item) - PROD_B_001 (qty: 5)

Customer Identification

You can identify customers using any of these fields:

  • id: Zendera internal customer ID
  • externalId: Your system’s customer identifier
  • businessName: Customer name for lookup

Resolving order types

orderType accepts an id or a name. If you only know the name and want to validate it (or need the id) before importing:

GET /v2/order-types/search?name=Delivery

Returns the matching order type — order_type_id, order_type_name, priority, pick_up_before / deliver_before, is_active, and booking constraints (min_book_ahead_minutes / max_book_ahead_minutes). A name with no match returns 404; an import referencing an unknown order type fails with ORDER_TYPE_NOT_FOUND_CODE.

Product Hierarchy Fields

  • externalId: Your product identifier
  • parentExternalId: Parent product’s external ID, used to build the hierarchy
  • atomType: Either "colli" or "trade item"
  • externalInstanceNumber: Optional unique instance identifier for individual product instances (used as the internal order-product number when set; otherwise the system falls back to externalId)

Location Fields

The pickup and delivery objects (LocationRequest) support the following commonly used fields:

  • name / externalId: Location name and your external identifier
  • address: Structured address (address1, city, postalCode, country, …)
  • earliest / latest: ISO 8601 timestamps for a fixed time window — see Time windows
  • opens / closes: Opening hours (with hasOpeningTimes / openingHoursIsDefault) — lets Zendera derive the window instead
  • estimatedTimeOnLocation: Minutes expected to spend at the location
  • sendNotificationBeforeArriving: Enable/disable customer notifications
  • contacts: Array of contacts for the location
  • instructions / customerInstructions / parkingInstructions: Free-text instructions
  • skills: Array of required/prohibited skills for the location

Time windows: earliest/latest vs opens/closes

The two field pairs behave very differently — pick deliberately:

  • earliest + latest (both set) is a fixed window. It is used verbatim as the stop’s time window, overriding everything configured on the location in Zendera. Use it when your system is the source of truth for this specific order’s window.
  • Without earliest/latest, Zendera derives the window from the location — which means dispatchers (orgadmin/orguser) stay in control of time windows from the Zendera web application.

When earliest/latest are not both set, the window for the order’s date is resolved in this order:

  1. The location’s time windows configured in Zendera (the recurring RRule windows shown on the Locations page).
  2. The location’s stored default opening hours — applies when the location has opening times saved and its “use as default time window” setting is on.
  3. The opens / closes clock times from your request (requires hasOpeningTimes: true and openingHoursIsDefault set) — these also seed the stored opening hours when the import creates the location.
  4. If none of the above produce a window, Zendera falls back to the order type’s time window (derived from readyFrom); if that fails too, the order is rejected with an invalid-time-window reason.

Note the precedence: location configuration in Zendera (steps 1–2) wins over request opens/closes (step 3). So sending opens/closes seeds the location’s hours, but once dispatchers configure windows or default hours in Zendera, those take over — only earliest/latest overrides them per order.

Freight Fields

Freight is an entity used to group multiple orders together. To assign an order to a freight, provide all three of the following fields:

  • internalFreightNumber: Unique identifier for the freight
  • consignee: The receiving party
  • consignor: The sending party

The consignee and consignor are identified the same way as customers — using any of id (Zendera internal ID), externalId (your system’s identifier), or name / businessName.

{ "orders": [ { "externalId": "ORDER_123", "internalFreightNumber": "FREIGHT_001", "consignee": { "id": 456, "businessName": "Receiving Company" }, "consignor": { "externalId": "WAREHOUSE_01", "businessName": "Main Warehouse" } // … other fields } ] }

Update Configuration

The updateConfig object (v3.UpdateSettings) controls how existing data is handled when re-importing an order with the same externalId. Each area can independently choose its behaviour.

FieldTypeControls
orderLocationenumHow existing pickup/delivery locations are handled
orderProductsenumHow existing products on the order are handled
orderenumHow the existing order itself is handled
orderGroupsenumHow existing group memberships are handled
orderPricingenumHow existing pricing entries are handled (ignored if not set)
allowMergebool (nullable)Whether ERP orders may be merged into a single order
appendStopContactsbool (nullable)Append contacts from both the request and Zendera
appendStopInstructionsbool (nullable)Append instructions from both the request and Zendera
appendStopParkingInstructionsbool (nullable)Append parking instructions from both sources
appendStopCustomerInstructionsbool (nullable)Append customer instructions from both sources

Order Location Update Behavior (orderLocation)

  • "UNKNOWN_UPDATE_ORDER_LOCATION_BEHAVIOR" (default): Use system default behavior
  • "IGNORE_EXISTING_ORDER_LOCATION_BEHAVIOR": Skip updates to existing locations
  • "REPLACE_EXISTING_ORDER_LOCATION_BEHAVIOR": Completely replace existing location data

Product Update Behavior (orderProducts)

  • "UNKNOWN_UPDATE_PRODUCT_BEHAVIOR" (default): Use system default behavior
  • "IGNORE_EXISTING_ORDER_PRODUCT": Skip updates to existing products
  • "REPLACE_EXISTING_ORDER_PRODUCT": Replace all product data
  • "REPLACE_IF_NOT_MODIFIED_ORDER_PRODUCT": Replace only if the product hasn’t been manually modified

Order Update Behavior (order)

  • "UNKNOWN_UPDATE_ORDER_BEHAVIOR" (default): Use system default behavior
  • "IGNORE_EXISTING_ORDER_BEHAVIOR": Skip updates to the existing order
  • "REPLACE_EXISTING_ORDER_BEHAVIOR": Replace order data

Order Group Update Behavior (orderGroups)

  • "UNKNOWN_UPDATE_ORDER_GROUP_BEHAVIOR" (default): Use system default behavior
  • "IGNORE_EXISTING_ORDER_GROUP_BEHAVIOR": Skip updates to existing groups
  • "REPLACE_EXISTING_ORDER_GROUP_BEHAVIOR": Replace group memberships

Pricing Update Behavior (orderPricing)

  • "UNKNOWN_UPDATE_PRICING_BEHAVIOR" (default): Use system default behavior
  • "IGNORE_EXISTING_PRICING_BEHAVIOR": Skip updates to existing pricing
  • "REPLACE_EXISTING_PRICING_BEHAVIOR": Replace pricing entries

Order Merging

Order merging allows multiple ERP orders to be consolidated into a single Zendera transport order. Set allowMerge in updateConfig:

  • true: Enable order merging for this import
  • false: Keep ERP orders separate
  • null: Use the organization default setting

When orders are merged, the corresponding result is returned as orderMerged (see Response Structure).

The detailed merge-matching rules (freight, route, pickup/delivery location, status, and volume constraints) are governed by internal server-side logic and feature flags, not by fields exposed in this API. Enable allowMerge and rely on the orderMerged result type to detect merges; do not depend on internal field names such as InternalFreightNumber or RouteNumber for merge behaviour. (The internalFreightNumber and routeNumber request fields are available, but the merge decision itself is not part of the public contract.)

Response Structure

The response is an ImportResponse containing one result per imported order. Each result is exactly one of five outcome types.

{ "results": [ { "orderCreated": { "OrderID": 12345, "externalId": "ORDER_123" } }, { "draftCreated": { "DraftID": 7890, "externalId": "ORDER_124", "reasons": [{ "code": "INVALID_PICKUP_ADDRESS_CODE" }] } }, { "orderUpdated": { "OrderID": 12300, "externalId": "ORDER_125" } }, { "orderMerged": { "OrderID": 12200, "externalId": "ORDER_126" } }, { "failedUpdate": { "externalId": "ORDER_127", "reasons": [{ "code": "CUSTOMER_NOT_FOUND_CODE" }] } } ] }

The five result types per order:

OutcomePayloadWhat it meansWhat to do
orderCreatedOrderID, externalIdNew order successfully created.Store OrderID.
draftCreatedDraftID, externalId, reasons[]Validation partially failed; a draft exists for human review in the UI.Surface to a user — drafts don’t appear in your normal order pipeline.
orderUpdatedOrderID, externalIdExisting order updated based on updateConfig.Confirm in your system.
orderMergedOrderID, externalIdOrder merged into an existing order.Confirm.
failedUpdateexternalId, reasons[]Update or merge failed.Read the reasons codes, fix, retry.

Response identifiers use PascalCase (OrderID, DraftID), unlike request fields which are camelCase.

Error Handling

draftCreated and failedUpdate results carry a reasons array, where each entry has a code from the following set:

  • CUSTOMER_NOT_FOUND_CODE: Customer lookup failed
  • VEHICLE_TYPE_NOT_FOUND_CODE: Vehicle type not found
  • ORDER_TYPE_NOT_FOUND_CODE: Order type not found
  • INVALID_PRODUCT_CODE / INVALID_PRODUCT_SKILL_CODE: Product or product skill validation failed
  • INVALID_PICKUP_ADDRESS_CODE / INVALID_PICKUP_TIMEWINDOW_CODE / MISSING_PICKUP_CODE / INVALID_PICKUP_SKILL_CODE: Pickup validation failed
  • INVALID_DELIVERY_ADDRESS_CODE / INVALID_DELIVERY_TIMEWINDOW_CODE / MISSING_DELIVERY_CODE / INVALID_DELIVERY_SKILL_CODE: Delivery validation failed
  • INVALID_GROUP_CODE: Group validation failed
  • UNKNOWN_INVALID_INPUT_CODE: Unspecified validation failure

Transport- or request-level errors return a standard error response with a code, message, and details.

Validation failures often create drafts, not rejections. Watch for draftCreated results and surface them — they do not appear in your normal order pipeline. Drafts are managed via the Order Drafts API, where completing one auto-promotes it to a real order.

Legacy import endpoints (v1/v2) and cancellation

Two older REST import endpoints still exist and remain available to API keys:

PUT /v1/import/orders PUT /v2/import/orders

Both take { "customer": ..., "order_type": ..., "vehicle_type": ..., "orders": [...] }. The v2 variant additionally computes pricing during import; v1 does not and returns errors as plain text. For new integrations use POST /v3/orders/import (this page) — it has the richest validation, update behavior configuration, and structured per-order results.

One legacy endpoint is still genuinely useful regardless of import version:

DELETE /v1/import/orders/{internalOrderNumber}

Cancels the order with that internalOrderNumber (sets the order and its stops to cancelled — nothing is deleted). 204 on success, 404 when no such order exists. It is equivalent in effect to POST /v2/orders/cancel-orders for a single order.

What’s next

Last updated on