# Working with the Data Model — Warehousing and Logistics

Use Warehousing and Logistics to follow materials into the business, through production and out to the customer. It brings stock, reservations, planned deliveries, shipments and delivery confirmations into one working guide, even when they appear in different areas of the application.

For a printing business, the flow might begin with paper arriving from a supplier and end with pallets of finished brochures reaching several destinations. The records answer different questions along that journey: what is available, what a job needs, what the customer expects, what was packed and what was actually received.

**Use IaC for infrastructure and provisioning. Use the API for runtime data.** Provision warehouse locations and carriers through IaC, together with the suppliers, substrate catalogue and integrations used by your business. Use the API to receive stock, reserve materials, record production issues, plan deliveries, prepare shipments and record delivery outcomes.

This guide covers every operation in the `logistics` topic, including shared tools and the handover to production and ERP. Warehousing and logistics form one workflow here; their individual records retain their own identities and purposes. The [Warehousing and Logistics API reference](/docs/data-model/logistics-reference) lists every operation's arguments and results, followed by input, record and enum tables.

## Choose where to start

| What you need to do | Start here |
| --- | --- |
| Find a storage location or carrier | [Locations and carriers](#read-locations-and-carriers) |
| Receive paper, establish stock or count a location | [Warehouse stock](#receive-and-count-warehouse-stock) |
| Reserve paper and supply a printing job | [Materials for production](#supply-materials-to-a-printing-job) |
| Record where and when the customer needs the work | [Delivery planning](#plan-the-customer-delivery) |
| Specify packing, handling or delivery requirements | [Delivery requirements](#record-delivery-requirements) |
| Create a shipment, pallet or parcel | [Packing and dispatch](#prepare-shipments-and-packages) |
| Record quantities dispatched, received or returned | [Delivery confirmation](#record-dispatch-receipt-and-returns) |
| Check outstanding quantities and complete an order | [Fulfilment review](#review-fulfilment-and-complete-the-order) |
| Investigate related records and possible effects | [Traceability](#trace-the-work-and-its-impact) |

## Read and change records safely

**Start with `create…` for a new record and `update…` for changes to an existing record.** These are the recommended operations for everyday work. Check the required fields and the treatment of omitted values in each input: an update is not always a partial patch. In particular, planned delivery updates use the same required input shape as creation.

Treat `upsert…` as an advanced option. Use it only when you understand how it identifies a record, which values it requires and what it replaces. Warehouse stock and transfer upserts can replace the whole record. They are not general shortcuts for editing one field or finding a record by an external reference.

Send the operation and its variables separately to your environment's GraphQL API using an authenticated account. Check top-level GraphQL `errors` and the `errors { path message }` inside mutation results. Continue only when errors are empty and the expected record is present. Keep the returned IDs for later requests.

Where an operation requires `expectedVersion`, read the record's current `version` first. If another change has intervened, read it again and reconcile your changes. Do not repeatedly submit the old version. Where an input accepts a `requestKey`, keep the key stable for a retry of the same request and use a new key for a different observation; this does not give other operations retry protection.

The examples follow the same **5,000-copy brochure job** as the Production guide. The paper examples use an illustrative cover requirement of **2,500 sheets**; the first outbound shipment contains **2,000 finished brochures**. Sheets and finished copies are different units and different records. Replace every illustrative ID, date, destination and quantity with the values for your job. These are separate recipes, not a script to execute unchanged from top to bottom.

## Read locations and carriers

A warehouse location identifies where stock is held: a warehouse, area, rack or other configured storage position. Locations can have parent locations. Read their names, codes, types and active status before selecting one for a receipt or material reservation.

A carrier identifies the organisation carrying the shipment. Read the configured carrier when preparing dispatch. Selecting it on a shipment does not itself book a collection or purchase a shipping label.

Use `getWarehouseLocation` or `listWarehouseLocations` to find storage locations, and `getCarrier` or `listCarriers` to find carriers. Their create, update, upsert and delete operations are included in the reference for completeness; manage those definitions through IaC.

| Operation | Use it to |
| --- | --- |
| `Query.getWarehouseLocation` | Read a warehouse location. |
| `Query.listWarehouseLocations` | Browse warehouse locations. |
| `Query.getCarrier` | Read a carrier. |
| `Query.listCarriers` | Browse carriers. |
| `Mutation.createWarehouseLocation` | Create a warehouse location. Manage through IaC. |
| `Mutation.updateWarehouseLocation` | Change a warehouse location. Manage through IaC. |
| `Mutation.upsertWarehouseLocation` | Advanced: Create or replace a warehouse location. Prefer create/update for routine changes. Manage through IaC. |
| `Mutation.deleteWarehouseLocation` | Delete a warehouse location. Manage through IaC. |
| `Mutation.upsertCarrier` | Advanced: Create or replace a carrier. Prefer create/update for routine changes. Manage through IaC. |
| `Mutation.createCarrier` | Create a carrier. Manage through IaC. |
| `Mutation.updateCarrier` | Change a carrier. Manage through IaC. |
| `Mutation.deleteCarrier` | Delete a carrier. Manage through IaC. |

## Receive and count warehouse stock

A warehouse stock record holds a quantity of a particular material or item at a location, together with its unit, reserved quantity and available quantity. A material lot adds batch identity and provenance. It does not hold a second, separate stock balance.

### Receive paper against a purchase order

Use `receivePurchaseOrder` when paper arrives against an existing purchase order:

```graphql example=logistics.receive-paper part=operation
mutation ReceiveLogisticsPaper($input: ReceivePurchaseOrderInput!) {
  receivePurchaseOrder(input: $input) {
    purchaseOrder { id status }
    updatedLines { id receivedQuantity }
    stockEntries { id quantity unit availableQuantity }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.receive-paper part=variables
{
  "input": {
    "purchaseOrderId": "pur_000000a48h848r248h248h248h",
    "warehouseLocationId": "wlc_000000448h848r248h248h248h",
    "receivedLines": [
      {
        "purchaseOrderLineId": "pul_000000c48h848r248h248h248h",
        "receivedQuantity": 3000
      }
    ]
  }
}
```

`receivedQuantity` is the quantity arriving in this receipt, not the new lifetime total for the line. The operation adds it to the line's received quantity. Supplying a warehouse location creates stock entries for received lines that identify a substrate. A service line or another line without a substrate does not become paper stock merely because a location was supplied.

Inspect `updatedLines` and `stockEntries` in the result. Read the purchase order before retrying after a lost response, since repeating a receipt can add the quantity again. Use each purchase order line once in a receipt request. The purchase order moves to partially received or received according to the accumulated line quantities.

The topic also includes `createPurchaseOrder`, which creates a purchase order with lines and calculates totals. Use it when you need that combined purchasing action. Supplier agreements, prices and broader purchasing workflows are covered in the [ERP guide](/docs/data-model/erp).

### Establish a stock record directly

For an opening balance or another supported stock-entry process, use `createWarehouseStock`:

```graphql example=logistics.record-opening-stock part=operation
mutation RecordLogisticsOpeningStock($input: CreateWarehouseStockInput!) {
  createWarehouseStock(input: $input) {
    warehouseStock { id quantity reservedQuantity availableQuantity unit }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.record-opening-stock part=variables
{
  "input": {
    "locationId": "wlc_000000448h848r248h248h248h",
    "stockableType": "SUBSTRATE",
    "stockableId": "job_000000648h848r248h248h248h",
    "quantity": 3000,
    "unit": "SHEET",
    "batchNumber": "PAPER-2026-042",
    "notes": "Opening balance from verified physical stock; not a second receipt."
  }
}
```

This is an alternative to recording the same stock through a purchase-order receipt. Do not do both for the same delivery. `stockableType` identifies the kind of item, and `stockableId` identifies the actual configured material or item. For paper, select an existing substrate and use the unit in which the stock is held.

Available stockable types are:

`SUBSTRATE`, `COMPONENT`, `FINISHED_GOODS`, `TOOL`, `INK`, `CONSUMABLE`

The measurement units are:

`CLICK`, `CM`, `FT`, `G`, `IMPRESSION`, `IN`, `KG`, `LB`, `M`, `MM`, `OZ`, `PC`, `PT`, `SHEET`

Use `getWarehouseStock` or `listWarehouseStocks` to inspect balances, including `quantity`, `reservedQuantity` and `availableQuantity`. Keep units consistent between stock and material requirements; a numeric quantity alone does not establish a conversion between kilograms, sheets and pieces.

Where your environment exposes material-lot fields, a stock entry can reference an existing lot or create its lot metadata alongside the stock. Keep the lot's material and unit consistent with the stock. Read quantities from the stock record, not from the lot.

### Count what is physically there

Use `performStockCount` to record the actual count at a location:

```graphql example=logistics.count-stock part=operation
mutation CountLogisticsStock($input: PerformStockCountInput!) {
  performStockCount(input: $input) {
    updatedStocks { id quantity lastCountedAt }
    adjustments { id quantity reason notes }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.count-stock part=variables
{
  "input": {
    "locationId": "wlc_000000448h848r248h248h248h",
    "counts": [
      {
        "warehouseStockId": "wst_000000848h848r248h248h248h",
        "countedQuantity": 2980
      }
    ]
  }
}
```

`countedQuantity` is the physical total you counted for that stock record. It is not an adjustment amount. The operation updates the stock quantity, records the count time and records differences as adjustments. Include each stock record once, and reconcile existing reservations if the physical count reveals a shortage.

For ordinary record details, use `updateWarehouseStock` with the record ID and the fields you intend to change. Prefer the receipt, issue and count operations for their corresponding business events, so the associated records are created too.

### Record movements carefully

A warehouse transfer records a movement's source, destination, material, quantity, reason and status. Use `createWarehouseTransfer` for a new movement record and `updateWarehouseTransfer` to revise it. Those operations maintain the transfer record; they do not by themselves rebalance stock at both locations. Do not assume that setting a transfer to completed has moved quantities in the stock records.

Use the appropriate stock-changing process for the physical event, and reconcile both locations when arranging an internal move. The production-issue and stock-count operations create their associated transfer or adjustment records as part of those actions. Deleting a transfer is not an automatic reversal of a physical movement.

| Operation | Use it to |
| --- | --- |
| `Query.getWarehouseStock` | Read a warehouse stock. |
| `Query.listWarehouseStocks` | Browse warehouse stocks. |
| `Query.getWarehouseTransfer` | Read a warehouse transfer. |
| `Query.listWarehouseTransfers` | Browse warehouse transfers. |
| `Mutation.upsertWarehouseStock` | Advanced: Create or replace a warehouse stock. Prefer create/update for routine changes. |
| `Mutation.createWarehouseStock` | Create a warehouse stock. |
| `Mutation.updateWarehouseStock` | Change a warehouse stock. |
| `Mutation.deleteWarehouseStock` | Delete a warehouse stock. |
| `Mutation.upsertWarehouseTransfer` | Advanced: Create or replace a warehouse transfer. Prefer create/update for routine changes. |
| `Mutation.createWarehouseTransfer` | Create a warehouse transfer. |
| `Mutation.updateWarehouseTransfer` | Change a warehouse transfer. |
| `Mutation.deleteWarehouseTransfer` | Delete a warehouse transfer. |
| `Mutation.createPurchaseOrder` | Create a purchase order and its lines, calculating the totals. |
| `Mutation.receivePurchaseOrder` | Record quantities received against purchase order lines and optionally create stock entries. |
| `Mutation.performStockCount` | Set counted stock quantities and record the differences. |

## Supply materials to a printing job

A job material reservation describes what a printing job needs. The reservation record and the act of reserving available warehouse stock are separate steps.

### Record the requirement

```graphql example=logistics.request-material part=operation
mutation RequestLogisticsMaterial($input: CreateJobMaterialReservationInput!) {
  createJobMaterialReservation(input: $input) {
    jobMaterialReservation { id jobId requiredQuantity reservedQuantity unit status }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.request-material part=variables
{
  "input": {
    "jobId": "job_000000248h848r248h248h248h",
    "stockableType": "SUBSTRATE",
    "stockableId": "job_000000648h848r248h248h248h",
    "requiredQuantity": 2500,
    "unit": "SHEET",
    "preferredLocationId": "wlc_000000448h848r248h248h248h",
    "notes": "Cover press sheets; quantity agreed from the production plan."
  }
}
```

The 2,500-sheet requirement must come from the agreed production plan, including the intended count basis and allowances. It is not derived simply from the order for 5,000 brochures. The preferred location guides selection; it does not create a stock balance there.

### Reserve available stock

```graphql example=logistics.reserve-material part=operation
mutation ReserveLogisticsMaterial($input: ReserveJobMaterialsInput!) {
  reserveJobMaterials(input: $input) {
    reservations { id requiredQuantity reservedQuantity status }
    fullyReservedCount partiallyReservedCount unmetCount
    errors { path message }
  }
}
```

Variables:

```json example=logistics.reserve-material part=variables
{
  "input": {
    "jobId": "job_000000248h848r248h248h248h"
  }
}
```

Read every returned reservation and the fully reserved, partially reserved and unmet counts. The operation attempts to satisfy pending or partially reserved requirements from available matching stock, favouring the preferred location. A successful request does not mean every requirement was fully met.

Reservation statuses are:

`PENDING`, `RESERVED`, `PARTIALLY_RESERVED`, `CONSUMED`, `CANCELLED`

Use create/update for the requirement records. Do not assume that editing or deleting a reservation record also releases or recalculates every stock reservation; reconcile the stock balances as part of your material-control process.

### Issue stock to production

`issueStockToProduction` records a production issue, creates transfer records and reduces warehouse stock. Each item identifies the stock record, quantity and optional job reference. Include each stock record once in the request.

This operation checks **available quantity**, which excludes reserved stock. It does not consume a job reservation automatically. A reservation followed by an issue is therefore not a complete automatic reservation-consumption workflow: reconcile or release the relevant reservation through your stock-control process before issuing that stock. Do not subtract it twice or change a reservation status and assume the stock balance has followed.

Production reports record what was used or produced on the shop floor. They serve a different purpose from the warehouse issue; keep the two consistent when reviewing material use.

| Operation | Use it to |
| --- | --- |
| `Query.getJobMaterialReservation` | Read a job material reservation. |
| `Query.listJobMaterialReservations` | Browse job material reservations. |
| `Mutation.upsertJobMaterialReservation` | Advanced: Create or replace a job material reservation. Prefer create/update for routine changes. |
| `Mutation.createJobMaterialReservation` | Create a job material reservation. |
| `Mutation.updateJobMaterialReservation` | Change a job material reservation. |
| `Mutation.deleteJobMaterialReservation` | Delete a job material reservation. |
| `Mutation.reserveJobMaterials` | Attempt to reserve available stock for pending job material requirements. |
| `Mutation.issueStockToProduction` | Issue available warehouse stock to production, recording transfers and reducing quantities. |

## Plan the customer delivery

A planned delivery records the customer's intended destination and arrival requirement. Its items state what is expected there. A shipment records a particular physical dispatch. One planned delivery can be supplied by several shipments; planning a quantity does not mean it has left the building.

### Record the destination and arrival requirement

```graphql example=logistics.plan-delivery part=operation
mutation PlanLogisticsDelivery($input: PlannedDeliveryInput!) {
  createPlannedDelivery(input: $input) {
    plannedDelivery { id version destination { recipientOrganization city country } }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.plan-delivery part=variables
{
  "input": {
    "destination": {
      "snapshot": {
        "recipientOrganization": "North Street Books",
        "street1": "18 Example Road",
        "city": "Bristol",
        "postalCode": "BS1 1AA",
        "country": "GB"
      }
    },
    "requestedArrival": {
      "meaning": "REQUIRED_BY",
      "latest": {
        "date": "2026-11-06"
      },
      "timezone": "Europe/London"
    }
  }
}
```

The destination can be supplied as a snapshot or selected from an existing address using the corresponding input. Supply the intended choice rather than both. The saved destination is the delivery's address record; review it before dispatch if the customer's address changes.

This example requests arrival by a date in the destination's time zone. Use an offset-aware `instant` when you know a precise time; do not turn an unknown time into midnight. `requestedArrival` distinguishes a requested date, deadline, window or appointment.

### Specify the finished work to deliver

```graphql example=logistics.plan-delivery-item part=operation
mutation PlanLogisticsDeliveryItem($input: PlannedDeliveryItemInput!) {
  createPlannedDeliveryItem(input: $input) {
    plannedDeliveryItem { id version requestedQuantity allocatedQuantity }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.plan-delivery-item part=variables
{
  "input": {
    "plannedDeliveryId": "pdl_000000e48h848r248h248h248h",
    "jobId": "job_000000248h848r248h248h248h",
    "orderLineId": "oln_000001848h848r248h248h248h",
    "description": "Autumn brochures — finished copies",
    "requestedQuantity": 5000
  }
}
```

This item requests 5,000 finished brochures. Keep the job and order-line links where they apply, so production and commercial fulfilment can be reviewed together. Use separate planned items when destinations or delivery obligations differ.

Updates and deletions of planned deliveries and their items require the current version. Creation and update validate the records; they do not prove that packing, dispatch or receipt has happened.

### Connect a physical dispatch to the plan

After creating the shipment item in the next section, allocate its quantity to the planned delivery item:

```graphql example=logistics.allocate-delivery part=operation
mutation AllocateLogisticsDelivery($input: ShipmentItemAllocationInput!) {
  createShipmentItemAllocation(input: $input) {
    shipmentItemAllocation { id version quantity shipmentItemId plannedDeliveryItemId }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.allocate-delivery part=variables
{
  "input": {
    "shipmentItemId": "sit_000001248h848r248h248h248h",
    "plannedDeliveryItemId": "pdi_000000g48h848r248h248h248h",
    "quantity": 2000
  }
}
```

The first dispatch supplies 2,000 of the requested 5,000 brochures. Keep the allocation ID and version. An allocation is a planned connection, not a receipt confirmation. Later, recorded dispatch or receipt quantities must be attributed to the appropriate planned item before they can be credited to it.

| Operation | Use it to |
| --- | --- |
| `Query.getPlannedDelivery` | Read a planned delivery. |
| `Query.getPlannedDeliveryItem` | Read a planned delivery item. |
| `Query.listPlannedDeliveryItems` | Browse planned delivery items. |
| `Query.getShipmentItemAllocation` | Read a shipment item allocation. |
| `Query.listShipmentItemAllocations` | Browse shipment item allocations. |
| `Mutation.createPlannedDelivery` | Create a planned delivery. |
| `Mutation.updatePlannedDelivery` | Change a planned delivery. |
| `Mutation.deletePlannedDelivery` | Delete a planned delivery. |
| `Mutation.createPlannedDeliveryItem` | Create a planned delivery item. |
| `Mutation.updatePlannedDeliveryItem` | Change a planned delivery item. |
| `Mutation.deletePlannedDeliveryItem` | Delete a planned delivery item. |
| `Mutation.createShipmentItemAllocation` | Create a shipment item allocation. |
| `Mutation.updateShipmentItemAllocation` | Change a shipment item allocation. |
| `Mutation.deleteShipmentItemAllocation` | Delete a shipment item allocation. |

## Record delivery requirements

Delivery requirements describe obligations beyond a quantity and address: for example, handling instructions, packaging requirements or an agreed logistics event. Keep these structured requirements with the delivery or planned item so they can be assessed alongside the quantities.

`createCustomerFulfillmentDefault` records a reusable customer default. Read or revise it through the corresponding get, list and update operations. `resolveCustomerFulfillmentDefaults` explicitly copies a customer's defaults into a planned delivery; it requires the delivery's current version. Changing a customer default is not a silent rewrite of every existing delivery.

Use `createFulfillmentRequirement` for a delivery-specific requirement or an explicit replacement, supplying the relevant scope and value. Read the existing requirement before replacing it, and preserve the reason for the change. Retire requirements and defaults when they no longer apply, using their current versions. There is no ordinary update operation for a fulfilment requirement; create the intended replacement or retire the old requirement as appropriate.

`effectiveFulfillmentRequirements` brings together the copied and local requirements and their explicit exceptions. Review its diagnostics rather than assuming that a list of requirements is already satisfied.

Use `recordRequirementEvidence` to record an observation about whether a requirement was met. Its input distinguishes the observation, completion indication, occurrence time and attribution. Read the supporting records through `getRequirementEvidence` and `listRequirementEvidence`. Recording an observation does not itself send a message, obtain approval or perform the physical handling step.

`assessFulfillmentRequirements` evaluates the relevant scope for the selected quantity stage and, where appropriate, logistics event and time. Review unresolved results. A requirement assessment and a quantity balance answer different questions; both matter when deciding whether the work is ready to close.

| Operation | Use it to |
| --- | --- |
| `Query.getFulfillmentRequirement` | Read a fulfillment requirement. |
| `Query.listFulfillmentRequirements` | Browse fulfillment requirements. |
| `Query.getCustomerFulfillmentDefault` | Read a customer fulfillment default. |
| `Query.listCustomerFulfillmentDefaults` | Browse customer fulfillment defaults. |
| `Query.effectiveFulfillmentRequirements` | Read applicable delivery requirements and explicit exceptions. |
| `Query.getRequirementEvidence` | Read a recorded observation about a delivery requirement. |
| `Query.listRequirementEvidence` | Browse observations about a delivery requirement. |
| `Query.assessFulfillmentRequirements` | Assess requirements for the requested quantity stage and logistics event. |
| `Mutation.createFulfillmentRequirement` | Create a fulfillment requirement. |
| `Mutation.createCustomerFulfillmentDefault` | Create a customer fulfillment default. |
| `Mutation.updateCustomerFulfillmentDefault` | Change a customer fulfillment default. |
| `Mutation.retireFulfillmentRequirement` | Retire a fulfillment requirement. |
| `Mutation.retireCustomerFulfillmentDefault` | Retire a customer fulfillment default. |
| `Mutation.resolveCustomerFulfillmentDefaults` | Copy the selected customer’s fulfilment defaults into a planned delivery. |
| `Mutation.recordRequirementEvidence` | Record whether a delivery requirement was met, with its supporting observation. |

## Prepare shipments and packages

A shipment groups a physical dispatch. Packages describe its pallets, cartons or parcels. Shipment items describe the work inside those packages. Preserve those distinctions when one job is split between vehicles or destinations.

### Create the first shipment

```graphql example=logistics.create-shipment part=operation
mutation CreateLogisticsShipment($input: CreateShipmentInput!) {
  createShipment(input: $input) {
    shipment { id shipmentNumber status }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.create-shipment part=variables
{
  "input": {
    "orderId": "ord_000001648h848r248h248h248h",
    "destinationSnapshot": {
      "recipientOrganization": "North Street Books",
      "street1": "18 Example Road",
      "city": "Bristol",
      "postalCode": "BS1 1AA",
      "country": "GB"
    },
    "shipmentNumber": "SHIP-2026-1042",
    "status": "DRAFT",
    "notes": "First 2,000 copies of the 5,000-copy brochure delivery."
  }
}
```

This draft covers the first 2,000 brochures. Set the actual configured carrier when it is known, and check the shipment destination against the planned delivery. The order link provides context; it does not allocate every item to an order line automatically.

### Add the pallet or parcel

```graphql example=logistics.add-package part=operation
mutation AddLogisticsPackage($input: CreateShipmentPackageInput!) {
  createShipmentPackage(input: $input) {
    shipmentPackage { id shipmentId packageNumber }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.add-package part=variables
{
  "input": {
    "shipmentId": "shp_000000j48h848r248h248h248h",
    "packageNumber": 1,
    "packagingType": "PALLET",
    "notes": "Pallet containing 2,000 finished brochures."
  }
}
```

The example uses one pallet. Add further package records for other pallets or parcels rather than treating a shipment's package-count field as the package contents. Supply dimensions and weights according to the conventions of your shipping process; this example leaves unknown measurements unset.

### Record the contents

```graphql example=logistics.add-shipment-item part=operation
mutation AddLogisticsShipmentItem($input: CreateShipmentItemInput!) {
  createShipmentItem(input: $input) {
    shipmentItem { id packageId quantity orderLineId }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.add-shipment-item part=variables
{
  "input": {
    "packageId": "spk_000001048h848r248h248h248h",
    "jobId": "job_000000248h848r248h248h248h",
    "orderLineId": "oln_000001848h848r248h248h248h",
    "description": "Autumn brochures — first dispatch",
    "quantity": 2000
  }
}
```

Use the returned item ID in the allocation recipe above. Use `updateShipment`, `updateShipmentPackage` and `updateShipmentItem` for revisions to existing records, and read the allocation records when changing quantities. Deleting a record does not undo a physical dispatch.

### Dispatch and track the shipment

For a combined action, `dispatchShipment` creates a shipment with packages and items already marked as picked up. Use it when that is the event you intend to record; do not call it to update the draft shipment created above, or you will create another shipment. It records the dispatch rather than booking transport on your behalf.

Use `updateShipmentTracking` for status and tracking changes. Supply occurrence times only when known. A status of `DELIVERED` is not an item-level receipt confirmation and does not by itself complete the order.

Shipment statuses are:

`DRAFT`, `READY_FOR_PICKUP`, `PICKED_UP`, `IN_TRANSIT`, `OUT_FOR_DELIVERY`, `DELIVERED`, `FAILED_DELIVERY`, `RETURNED`, `CANCELLED`

Use `dailyDispatchManifest` to review a day's shipments with their packages, items and order context. It is a view of the recorded dispatch work, not a substitute for checking what was physically handed to the carrier.

| Operation | Use it to |
| --- | --- |
| `Query.getShipment` | Read a shipment. |
| `Query.listShipments` | Browse shipments. |
| `Query.getShipmentPackage` | Read a shipment package. |
| `Query.listShipmentPackages` | Browse shipment packages. |
| `Query.getShipmentItem` | Read a shipment item. |
| `Query.listShipmentItems` | Browse shipment items. |
| `Query.listPlannedDeliveries` | Browse planned deliveries. |
| `Query.dailyDispatchManifest` | Read the day’s shipments with package, item and order details. |
| `Mutation.upsertShipment` | Advanced: Create or replace a shipment. Prefer create/update for routine changes. |
| `Mutation.createShipment` | Create a shipment. |
| `Mutation.updateShipment` | Change a shipment. |
| `Mutation.deleteShipment` | Delete a shipment. |
| `Mutation.createShipmentPackage` | Create a shipment package. |
| `Mutation.updateShipmentPackage` | Change a shipment package. |
| `Mutation.upsertShipmentPackage` | Advanced: Create or replace a shipment package. Prefer create/update for routine changes. |
| `Mutation.deleteShipmentPackage` | Delete a shipment package. |
| `Mutation.upsertShipmentItem` | Advanced: Create or replace a shipment item. Prefer create/update for routine changes. |
| `Mutation.createShipmentItem` | Create a shipment item. |
| `Mutation.updateShipmentItem` | Change a shipment item. |
| `Mutation.deleteShipmentItem` | Delete a shipment item. |
| `Mutation.dispatchShipment` | Create a shipment with packages and items already marked as picked up. |
| `Mutation.updateShipmentTracking` | Update shipment status and tracking details without completing the order. |

## Record dispatch, receipt and returns

Record what was observed for each shipment item: the quantity dispatched, received or returned. These quantity records are exposed through the `…Evidence` operations. A shipment status describes the shipment overall; these records establish the quantities for the individual items.

The quantity stages are:

`DISPATCH`, `RECEIPT`, `RETURN`

### Confirm the first receipt

```graphql example=logistics.record-receipt part=operation
mutation RecordLogisticsReceipt($input: ShipmentItemEvidenceInput!) {
  recordShipmentItemEvidence(input: $input) {
    shipmentItemEvidence { id version stage quantity }
    errors { path message }
  }
}
```

Variables:

```json example=logistics.record-receipt part=variables
{
  "input": {
    "shipmentItemId": "sit_000001248h848r248h248h248h",
    "stage": "RECEIPT",
    "quantity": 2000,
    "occurredAt": {
      "instant": "2026-11-05T10:15:00Z"
    },
    "requestKey": "receipt-SHIP-2026-1042-pallet-1-v1",
    "allocations": [
      {
        "source": {
          "allocation": {
            "id": "sia_000001448h848r248h248h248h",
            "version": 1
          }
        },
        "quantity": 2000
      }
    ]
  }
}
```

Only send this request once the 2,000 brochures have actually been received. It records that observation and attributes the quantity through the allocation created earlier. Replace the example's allocation version with the version you read from your own record. Use a stable `requestKey` for this observation so an identical retry can be recognised.

An observation can be recorded before its attribution is known. Use `attributeShipmentItemEvidence` later to credit it to the correct planned item, supplying the observation's current version, a request key and the attribution source. Quantities that have not been attributed must not be treated as satisfying the planned delivery.

### Correct a record or record a return

Use the correction fields to record a correction to an earlier observation, preserving its reference and explaining the reason. A return is a separate quantity stage linked to the relevant receipt through `receiptEvidenceId`; it is not simply a shipment status change. Inspect the returned records and balances after either action.

Read individual observations with `getShipmentItemEvidence` or their history with `listShipmentItemEvidence`. `shipmentItemExecutionBalance` shows observed, credited and unattributed quantities for the physical item. Missing observations remain unknown, including whether returns have been observed; do not turn an unknown value into zero.

### Read the planned delivery balance

```graphql example=logistics.read-delivery-balance part=operation
query ReadLogisticsDeliveryBalance($id: PlannedDeliveryItemID!) {
  plannedItemFulfillmentBalance(plannedDeliveryItemId: $id) {
    plannedDeliveryItemId requestedQuantity allocatedQuantity
    stages { stage knownCreditedQuantity attributionComplete remainingQuantity }
    netRetainedQuantity netAttributionComplete
  }
}
```

Variables:

```json example=logistics.read-delivery-balance part=variables
{
  "id": "pdi_000000g48h848r248h248h248h"
}
```

The requested quantity, allocated quantity and credited quantities are deliberately separate. After the first 2,000-copy receipt, the 5,000-copy obligation is not fully met. Inspect `attributionComplete` before interpreting `remainingQuantity`: the API can return an unknown remaining quantity when coverage or attribution is incomplete. Likewise, a missing `netRetainedQuantity` does not establish that there were no returns.

| Operation | Use it to |
| --- | --- |
| `Query.getShipmentItemEvidence` | Read a recorded shipment-item quantity observation. |
| `Query.listShipmentItemEvidence` | Browse shipment-item quantity observations. |
| `Query.shipmentItemExecutionBalance` | Review observed and attributed quantities for a shipment item. |
| `Query.plannedItemFulfillmentBalance` | Review allocated and credited quantities for a planned delivery item. |
| `Mutation.recordShipmentItemEvidence` | Record an observed dispatch, receipt or return quantity. |
| `Mutation.attributeShipmentItemEvidence` | Credit an observed quantity to its planned delivery item. |

## Review fulfilment and complete the order

Use `plannedItemReview` to review a planned item's quantities, effective requirements, assessment and authorised notes together. Resolve missing attribution and outstanding requirements rather than relying on the shipment's status label.

For the order-level review, use the same query as the [ERP guide](/docs/data-model/erp):

```graphql example=commerce.check-fulfilment part=operation
query CheckCommerceFulfilment($orderId: OrderID!) {
  orderFulfillmentCompletion(orderId: $orderId) {
    orderId completionPoint outcome diagnosticSummary diagnostics
  }
}
```

Variables:

```json example=commerce.check-fulfilment part=variables
{
  "orderId": "ord_01h455vb4pex5vsknk084sn02q"
}
```

`orderFulfillmentCompletion` assesses the current completion policy across the order's lines using explicitly attributed quantities. Read its outcome and diagnostics. Shipment totals or a single delivered pallet are not enough to conclude that the whole order has been fulfilled.

When the order satisfies the selected completion point, `completeOrderFulfillment` is the explicit action to complete it. Supply the completion point you reviewed and inspect the result. The operation checks the current policy; it does not invent missing receipt quantities or reopen previously completed work.

`updateFulfillmentSettings` changes shared completion policy. It is administrative configuration, not a step for closing an individual order. Keep that decision separate from recording what happened to a particular shipment.

| Operation | Use it to |
| --- | --- |
| `Query.plannedItemReview` | Review quantities, requirements and authorised notes for a planned delivery item. |
| `Query.orderFulfillmentCompletion` | Check whether every order line meets the current completion milestone |
| `Mutation.updateFulfillmentSettings` | Change shared order completion policy. Administrative configuration. |
| `Mutation.completeOrderFulfillment` | Complete an order after checking its fulfilment milestone |

## Trace the work and its impact

Use `fulfillmentTrace` to explore the relationships around a delivery or shipment record, and `fulfillmentImpact` to find related records upstream or downstream. This helps answer questions such as which planned deliveries are connected to a shipment item.

`provenanceTrace` and `provenanceImpact` extend the view across production, fulfilment and material provenance. A material batch can explain where work came from while warehouse stock remains the authority for quantities.

`whatIf` explores the effect of proposed relationship links and reports whether they introduce a cycle. It does not save those links or carry out the proposed movement. Read the allowed direction, depth and graph arguments in the reference before narrowing a trace.

These queries explain relationships and potential effects. They do not prove that a physical movement occurred or calculate a replacement stock balance.

| Operation | Use it to |
| --- | --- |
| `Query.fulfillmentTrace` | Trace relationships around a delivery or shipment record. |
| `Query.fulfillmentImpact` | Find delivery records affected upstream or downstream of a record. |
| `Query.provenanceTrace` | Trace relationships across production, fulfilment and material provenance. |
| `Query.provenanceImpact` | Find related records that could be affected across those relationships. |
| `Query.whatIf` | Explore the effect of proposed relationship links without saving them. |

## Keep handling instructions with the records

Use entity notes for operational instructions, conversations and decisions associated with the appropriate order, job or delivery record. Keep structured quantities, destinations, requirements and confirmations in their dedicated fields; a note explains the work rather than replacing those records.

This shared example records a note on the source order:

```graphql example=commerce.add-order-note part=operation
mutation AddCommerceOrderNote($input: CreateEntityNoteInput!) {
  createEntityNote(input: $input) {
    entityNote { id targetId body version }
    errors { path message }
  }
}
```

Variables:

```json example=commerce.add-order-note part=variables
{
  "input": {
    "targetId": "ord_01h455vb4pex5vsknk084sn02q",
    "body": "Customer requested delivery after 10:00. Confirm the delivery appointment before dispatch."
  }
}
```

Choose the target type and ID for the record you are annotating. Read the existing version before updating or deleting a note. Search and list operations help find relevant instructions; `reindexEntityNotes` supports search maintenance and is not a required step after writing each note.

| Operation | Use it to |
| --- | --- |
| `Query.getEntityNote` | Read a note attached to a record you can access |
| `Query.getEntityNoteBySource` | Find a note by its source integration and key |
| `Query.entityNotes` | Browse notes on a record |
| `Query.searchEntityNotes` | Search notes you are allowed to read |
| `Mutation.createEntityNote` | Add a note to a record |
| `Mutation.updateEntityNote` | Edit a note using its current version |
| `Mutation.deleteEntityNote` | Delete a note using its current version |
| `Mutation.reindexEntityNotes` | Request refreshed search indexing for a selected set of notes |

## Discover the API and get help

The shared discovery operations expose schema information, available tools and help. Use them to find the inputs supported by your environment and the related workflows you need.

Retrieve named examples through `topicExamples(names: [...])`, using names such as `logistics.receive-paper`, `logistics.plan-delivery` and `logistics.record-receipt`. Each operation belongs with its variables. Replace the illustrative IDs and review the intended business event before sending a mutation.

| Operation | Use it to |
| --- | --- |
| `Query.magicQuery` | Read data with a compact named query |
| `Query.getMagicQuerySchema` | Inspect the inputs and result of a compact named query |
| `Query.listMagicQueries` | Discover the available compact named queries |
| `Query.getMagicQueryGraphQL` | Inspect the GraphQL operation for a compact named query |
| `Query.listDataChangeEventTopics` | Discover record-change event topics |
| `Query.generateDefaultDataQuery` | Prepare a query for a record-change event |
| `Query.ping` | Check whether the API responds |
| `Query.iacSchema` | Read the infrastructure schema for IaC tooling |
| `Query.iacValidate` | Validate an IaC source document |
| `Query.iacGuides` | Read IaC authoring guides |
| `Query.helpChapters` | Discover help chapters |
| `Query.helpChapter` | Read a help chapter |
| `Query.searchHelp` | Find help by search term |
| `Query.schemaAuthoringCatalog` | Discover supported schema authoring features |
| `Query.compileSchemaXML` | Convert an authored XML schema to JSON Schema |
| `Query.renderSchemaXML` | Convert JSON Schema to authored XML |
| `Query.iacSchemaDocument` | Read a schema document used by IaC tooling |
| `Subscription.dataChangeEvents` | Subscribe to record-change events |
| `Query.topicExamples` | Retrieve named examples with their operations and variables. |