Orders Import API
v3The 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/latestwhen 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/importThe 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_HEREBase 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
| Field | Notes |
|---|---|
orders | One or more orders. Each item should include pickup, delivery, products, customer, vehicleType, orderType, and date, plus optional groups and pricing arrays. |
fallbackCustomer | Optional. Used for any order that does not specify its own customer. |
fallbackVehicleType | Optional. Used for any order that does not specify its own vehicleType. |
fallbackOrderType | Optional. Used for any order that does not specify its own orderType. |
updateConfig | Controls 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 IDexternalId: Your system’s customer identifierbusinessName: 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 identifierparentExternalId: Parent product’s external ID, used to build the hierarchyatomType: 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 toexternalId)
Location Fields
The pickup and delivery objects (LocationRequest) support the following commonly used fields:
name/externalId: Location name and your external identifieraddress: Structured address (address1,city,postalCode,country, …)earliest/latest: ISO 8601 timestamps for a fixed time window — see Time windowsopens/closes: Opening hours (withhasOpeningTimes/openingHoursIsDefault) — lets Zendera derive the window insteadestimatedTimeOnLocation: Minutes expected to spend at the locationsendNotificationBeforeArriving: Enable/disable customer notificationscontacts: Array of contacts for the locationinstructions/customerInstructions/parkingInstructions: Free-text instructionsskills: 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:
- The location’s time windows configured in Zendera (the recurring RRule windows shown on the Locations page).
- 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.
- The
opens/closesclock times from your request (requireshasOpeningTimes: trueandopeningHoursIsDefaultset) — these also seed the stored opening hours when the import creates the location. - 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 sendingopens/closesseeds the location’s hours, but once dispatchers configure windows or default hours in Zendera, those take over — onlyearliest/latestoverrides 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 freightconsignee: The receiving partyconsignor: 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.
| Field | Type | Controls |
|---|---|---|
orderLocation | enum | How existing pickup/delivery locations are handled |
orderProducts | enum | How existing products on the order are handled |
order | enum | How the existing order itself is handled |
orderGroups | enum | How existing group memberships are handled |
orderPricing | enum | How existing pricing entries are handled (ignored if not set) |
allowMerge | bool (nullable) | Whether ERP orders may be merged into a single order |
appendStopContacts | bool (nullable) | Append contacts from both the request and Zendera |
appendStopInstructions | bool (nullable) | Append instructions from both the request and Zendera |
appendStopParkingInstructions | bool (nullable) | Append parking instructions from both sources |
appendStopCustomerInstructions | bool (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 importfalse: Keep ERP orders separatenull: 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:
| Outcome | Payload | What it means | What to do |
|---|---|---|---|
orderCreated | OrderID, externalId | New order successfully created. | Store OrderID. |
draftCreated | DraftID, 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. |
orderUpdated | OrderID, externalId | Existing order updated based on updateConfig. | Confirm in your system. |
orderMerged | OrderID, externalId | Order merged into an existing order. | Confirm. |
failedUpdate | externalId, 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 failedVEHICLE_TYPE_NOT_FOUND_CODE: Vehicle type not foundORDER_TYPE_NOT_FOUND_CODE: Order type not foundINVALID_PRODUCT_CODE/INVALID_PRODUCT_SKILL_CODE: Product or product skill validation failedINVALID_PICKUP_ADDRESS_CODE/INVALID_PICKUP_TIMEWINDOW_CODE/MISSING_PICKUP_CODE/INVALID_PICKUP_SKILL_CODE: Pickup validation failedINVALID_DELIVERY_ADDRESS_CODE/INVALID_DELIVERY_TIMEWINDOW_CODE/MISSING_DELIVERY_CODE/INVALID_DELIVERY_SKILL_CODE: Delivery validation failedINVALID_GROUP_CODE: Group validation failedUNKNOWN_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/ordersBoth 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
- For recurring order templates that auto-generate orders on a schedule, see Recurring Orders.
- For completing imports that landed as drafts, see Order Drafts.
- For authentication details, see Authentication.