Warehouse Exceptions
6 API calls in this section.
Warehouse item exceptions for receiving, picking, inventory audits, and inventory handling. Source of truth is warehouse_exceptions. An item may have multiple open exceptions. Tenant-defined types may set write_off_stock=true; the exception flow is the sole owner of that stock write-off. Audit-created exceptions expose inventory_audit_id; their stock transaction references the audit directly and does not rewrite historical receiving quantities. An open damaged exception with an exact package mapping and current available warehouse stock exposes available_actions[].key=return_to_origin so only that box can be sent back while the parent order continues. Current stock is matched by tenant, inventory item, and warehouse when the exception predates stock creation and therefore has no inventory_stock_id; an explicit stock ID remains exact. receiving_order_id is optional audit provenance and is inferred from inventory custody metadata when available.
List Warehouse Exceptions
/warehousing/exceptionsLists warehouse item exceptions. Authentication requires the normal warehousing view access. Use filters for item, receiving order, picking order, warehouse, status, and type. Omit status to return both open and resolved history, including resolution details, resolver, and resolution time. Open damaged exceptions return the exact package_item_id and available_actions only when the item has current available unreserved stock in the exception warehouse. This includes stock created after the exception when inventory_stock_id is null; an explicit stock ID must still match exactly. The action endpoint revalidates stock and delivery state atomically. Receiving provenance does not control action visibility.
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_idOptional | string | UUID of the tenant. Optional when available from auth context. Example: |
statusOptional | string | Optional: open | resolved Example: |
inventory_item_idOptional | string | Optional item filter |
receiving_order_idOptional | string | Optional receiving order filter |
picking_order_idOptional | string | Optional picking order filter |
warehouse_idOptional | string | Optional warehouse filter |
exception_typeOptional | string | Optional exception type code filter |
page | string | Example: |
limit | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/warehousing/exceptions?tenant_id=${TENANT_ID}&status=open&page=1&limit=20" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json"Response examples
1{2 "success": true,3 "exceptions": [4 {5 "id": "open-exception-uuid",6 "inventory_item_id": "item-uuid",7 "receiving_order_id": "receiving-order-uuid",8 "exception_type": "damaged",9 "status": "open",10 "package_item_id": "package-item-uuid",11 "available_actions": [12 {13 "key": "return_to_origin",14 "exception_id": "open-exception-uuid",15 "package_item_id": "package-item-uuid"16 }17 ],18 "notes": "Box crushed on arrival",19 "reported_by_user": {20 "id": "reporter-uuid",21 "full_name": "Warehouse Receiver"22 },23 "resolved_by_user": null,24 "affected_quantity": 1,25 "exception_details": {26 "damage_area": "outer_box"27 },28 "item": {29 "id": "item-uuid",30 "name": "Product Name",31 "sku": "SKU-123"32 },33 "warehouse": {34 "id": "warehouse-uuid",35 "name": "Main Warehouse",36 "code": "WH-001"37 }38 },39 {40 "id": "resolved-exception-uuid",41 "inventory_item_id": "item-uuid",42 "receiving_order_id": "receiving-order-uuid",43 "exception_type": "wrong_item",44 "status": "resolved",45 "package_item_id": "package-item-uuid",46 "available_actions": [],47 "resolution_details": {48 "note": "Correct item confirmed"49 },50 "resolved_by": "resolver-uuid",51 "resolved_at": "2026-08-22T07:45:00Z",52 "resolved_by_user": {53 "id": "resolver-uuid",54 "full_name": "Warehouse Supervisor"55 },56 "affected_quantity": 1,57 "exception_details": {},58 "item": {59 "id": "item-uuid",60 "name": "Product Name",61 "sku": "SKU-123"62 },63 "warehouse": {64 "id": "warehouse-uuid",65 "name": "Main Warehouse",66 "code": "WH-001"67 }68 }69 ],70 "stats": {71 "totalExceptions": 2,72 "openCount": 1,73 "resolvedCount": 174 },75 "page": 1,76 "limit": 20,77 "total": 278}Available Warehouse Exception Types
/warehousing/exceptions/availableReturns exception type options available for a specific item/context. Type configuration comes from tenant override tenant_settings.warehouse_exception_types or system setting warehouse_exception_types. Each type returns write_off_stock; this behavior is configured by the tenant and must not be inferred from its code or label.
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | Example: |
inventory_item_idRequired | string | Required inventory item id Example: |
inventory_stock_idOptional | string | — |
receiving_order_idOptional | string | Optional receiving context Example: |
picking_order_idOptional | string | — |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/warehousing/exceptions/available?tenant_id=${TENANT_ID}&inventory_item_id=${INVENTORY_ITEM_ID}&receiving_order_id=${RECEIVING_ORDER_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json"Response examples
1{2 "success": true,3 "exception_types": [4 {5 "code": "missing",6 "label": "Missing",7 "description": "Expected item is missing during receiving or handling.",8 "write_off_stock": true9 }10 ],11 "all_exception_types": []12}Create Warehouse Exception
/warehousing/exceptionsCreates one open warehouse exception for a specific inventory item. Mutation access requires manage:operations:tenant, manage:module:warehouse, system admin, or a warehouse role.
Business rules
inventory_item_idandexception_typeare required.exception_typemust exist inwarehouse_exception_types.Context can be receiving (
receiving_order_id), picking (picking_order_id), stock (inventory_stock_id), or general inventory.receiving_order_idis optional; when omitted, a valid receipt recorded in the inventory item's custody metadata is captured as audit provenance.An item may have multiple independent open exceptions in the same context.
If one pending delivery already owns one completed picking order containing the item, creation captures that
picking_order_idas preparation context; ambiguous candidates remain recordable and are rejected safely only if resolution would need to restore preparation.If the selected tenant-defined type has
write_off_stock=trueand the linked receipt item has positivereceived_quantity,affected_quantitydefaults to 1 and must be positive. The exception insert atomically deducts available stock, releases any reservation that can no longer be satisfied, changes a zero-quantity stock row todepleted, reduces the receipt'sreceived_quantity, records the authenticated exception actor asreceived_bywhen the receipt had no actor, and writes astock_adjustmentinventory transaction. A fully received order changes toreceived_with_exceptions; a partially received order remainspartially_receivedso outstanding unreceived lines are not completed. This is the normal Lost/received-stock path.If the linked receipt item has
received_quantity=0, or omitsreceived_quantitybecause receiving has not started, the item has not entered warehouse stock. The exception is still created, no stock or receipt row is changed by the write-off trigger, unrelated stock for the same inventory item is not consumed, and the response returnsstock_written_off=falsewith null write-off audit fields.The exception response records
stock_written_off,written_off_quantity,written_off_by,written_off_at, andwritten_off_by_user.If positive received quantity exists but sufficient available stock cannot be written off, HTTP 409 returns code
warehouse_write_off_stock_not_available.Resolving the exception later follows the existing resolution behavior.
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
inventory_item_id | string | — |
receiving_order_id | string | — |
warehouse_id | string | — |
location_id | string | — |
exception_type | string | — |
affected_quantity | number | — |
exception_details | object | — |
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/warehousing/exceptions?tenant_id=${TENANT_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "inventory_item_id": "{{inventory_item_id}}",6 "receiving_order_id": "{{receiving_order_id}}",7 "warehouse_id": "{{warehouse_id}}",8 "location_id": "{{bin_location_id}}",9 "exception_type": "missing",10 "affected_quantity": 1,11 "exception_details": {},12 "notes": "Box could not be located"13}'Replace {{placeholders}} with your own values before sending.
Response examples
1{2 "success": true,3 "exception": {4 "id": "exception-uuid",5 "exception_type": "missing",6 "status": "open",7 "stock_written_off": true,8 "written_off_quantity": 1,9 "written_off_by": "user-uuid",10 "written_off_at": "2026-08-22T10:15:00Z",11 "written_off_by_user": {12 "id": "user-uuid",13 "full_name": "Warehouse Receiver"14 },15 "affected_quantity": 1,16 "exception_details": {},17 "notes": "Box could not be located",18 "item": {19 "id": "item-uuid",20 "name": "Product Name",21 "sku": "SKU-123"22 },23 "warehouse": {24 "id": "warehouse-uuid",25 "name": "Main Warehouse",26 "code": "WH-001"27 }28 },29 "open_exception": {30 "id": "exception-uuid",31 "exception_type": "missing",32 "exception_label": "Missing",33 "status": "open",34 "affected_quantity": 1,35 "stock_written_off": true,36 "written_off_quantity": 1,37 "written_off_by": "user-uuid",38 "written_off_at": "2026-08-22T10:15:00Z",39 "exception_details": {},40 "notes": "Box could not be located"41 }42}Get Warehouse Exception
/warehousing/exceptions/{{warehouse_exception_id}}Fetches one warehouse exception with item, stock, warehouse, location, optional receiving-order provenance, picking order, optional inventory_audit_id, reporter, resolver, and write-off audit context. For an open damaged stock-backed exception, package_item_id identifies the exact order box and available_actions contains return_to_origin when the mapping and available stock exist.
Path parameters
| Parameter | Type | Description |
|---|---|---|
warehouse_exception_idRequired | string | Variable used inside the request path. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/warehousing/exceptions/${WAREHOUSE_EXCEPTION_ID}?tenant_id=${TENANT_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json"Response examples
1{2 "success": true,3 "exception": {4 "id": "exception-uuid",5 "exception_type": "damaged",6 "status": "open",7 "package_item_id": "package-item-uuid",8 "available_actions": [9 {10 "key": "return_to_origin",11 "exception_id": "exception-uuid",12 "package_item_id": "package-item-uuid"13 }14 ]15 }16}Return Damaged Package to Origin
/warehousing/exceptions/{{warehouse_exception_id}}/return-to-originCreates and prepares one zero-fee internal reverse delivery from the current warehouse to the original sender for the exact damaged package linked to this exception. Requires warehouse mutation access. Tenant and system administrators may act without a warehouse assignment; other warehouse actors require an active staff assignment at the warehouse holding the package. notes is optional.
Business rules
The exception must be open and
damaged; the exact inventory/package mapping and positive unreserved warehouse stock are the custody authority. A receiving order is optional audit provenance, not a prerequisite.The inventory item must have an exact
delivery_package_item_id; ambiguous legacy mappings are rejected.The customer-bound staged outbound delivery must still be draft and unassigned.
At least one other eligible package must remain for the customer; otherwise use the existing whole-order return flow.
The reverse delivery uses
return_requests.default_delivery_service_id,delivery_flow=operational_return,delivery_fee=0,cod_amount=0, andpayment_status=paid. It creates no customer charge or refund.The source package leaves customer dispatch immediately through
return_status=return_approved, but its primary status remainspendinguntil the reverse delivery reaches the sender. Only then does the source package becomereturned.The source package is excluded from the customer-bound driver detail package list and its package/actionable counts while the return is approved, in transit, or completed. The separate reverse delivery keeps its own actionable package.
Each P&D pickup completes its existing assignment-owned handoff once; the legacy handoff fallback does not create a second handoff. Staged parent status updates do not create physical handoffs; the child legs own pickup. Customer-bound pickup and completion affect only customer-bound inventory, preserving the separate return package's stock, reservation, and custody.
Sender delivery completion sets the exact inventory item linked by
delivery_package_item_returnstoreturned_to_sendercustody. It does not select inventory through the original outbound link or change sibling inventory.The parent/root and customer-bound outbound delivery statuses are never changed by this action. Other packages continue normally.
The exception is resolved with the actor and timestamp. The action is idempotent for the same exception and returns HTTP 200 with
idempotent_replay=trueon replay.Package-return reverse deliveries can be retried or reassigned after an exception but cannot use generic cancellation, because cancelling would strand the required physical return.
Frontend integration contract
Show this action only when the exception/item response contains
available_actions[].key=return_to_origin.Call this endpoint with the
exception_id; do not call the whole-order return endpoint and do not send a package status.On success, use
data.return_delivery_idto open the reverse delivery anddata.package_return_statusfor its package-return state.HTTP 409 means the action is stale or ineligible; use the stable
codefor branching and show the localizedmessage.
Path parameters
| Parameter | Type | Description |
|---|---|---|
warehouse_exception_idRequired | string | Variable used inside the request path. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/warehousing/exceptions/${WAREHOUSE_EXCEPTION_ID}/return-to-origin?tenant_id=${TENANT_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "notes": "Box crushed during inbound handling"6}'Response examples
1{2 "success": true,3 "message": "The damaged package was prepared for return to the sender",4 "data": {5 "success": true,6 "action_key": "return_to_origin",7 "exception_id": "exception-uuid",8 "exception_status": "resolved",9 "resolved_by": "user-uuid",10 "resolved_at": "2026-09-11T14:30:00Z",11 "package_item_id": "package-item-uuid",12 "package_item_status": "pending",13 "package_return_id": "package-return-uuid",14 "package_return_status": "return_approved",15 "return_delivery_id": "return-delivery-uuid",16 "return_delivery_code": 1050,17 "return_delivery_status": "pending",18 "picking_order_id": "picking-order-uuid",19 "root_delivery_id": "root-delivery-uuid",20 "root_delivery_status": "pending",21 "outbound_delivery_id": "outbound-delivery-uuid",22 "outbound_delivery_status": "draft",23 "handoff_id": null,24 "idempotent_replay": false25 }26}Update Warehouse Exception
/warehousing/exceptions/{{warehouse_exception_id}}Updates or resolves a warehouse exception. Allowed statuses are open and resolved. Change a wrong reason with exception_type; if an open exception has not written off stock before and the corrected tenant-defined type has write_off_stock=true, the same atomic write-off is applied once. When that correction completes an in-progress receipt, the authenticated updater becomes received_by, the receipt becomes received_with_exceptions, and an item corrected to received_quantity=0 creates no stock and requires no bin. Changing away from that type or changing it again never restores or repeats the write-off. Resolve either a completed issue or a mistaken flag with status=resolved and an optional short note in resolution_details.note; resolution records resolved_by and resolved_at and cannot be reopened. A prior configured write-off is reversed exactly once. If this is the item's final open marker and one pending delivery still owns one valid completed picking order, the same stock is reserved back to that pick and item metadata records reservation_state=reserved; delivery, assignment, picking-order, and handoff statuses do not advance. If no valid preparation remains, restored stock stays available. Ambiguous or quantitatively inconsistent active preparation returns HTTP 409 warehouse_preparation_inconsistent and leaves the exception open.
Frontend integration contract
Continue sending the same resolution payload; no new request field is required.
On success, replace the local exception with the returned
exception.A missing unit with no physical receipt is a valid success and must not prompt for a bin.
Handle HTTP 409
warehouse_write_off_stock_not_availablefor real stock conflicts andwarehouse_preparation_inconsistentwhen existing picking/stock records require correction before resolution can finish.
Path parameters
| Parameter | Type | Description |
|---|---|---|
warehouse_exception_idRequired | string | Variable used inside the request path. |
Query parameters
| Parameter | Type | Description |
|---|---|---|
tenant_id | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
status | string | — |
resolution_details | object | — |
resolution_details.note | string | — |
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/warehousing/exceptions/${WAREHOUSE_EXCEPTION_ID}?tenant_id=${TENANT_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "status": "resolved",6 "resolution_details": {7 "note": "Issue sorted out"8 }9}'Response examples
1{2 "success": true,3 "exception": {4 "id": "exception-uuid",5 "status": "resolved",6 "picking_order_id": "picking-order-uuid",7 "resolution_details": {8 "note": "Issue sorted out"9 },10 "resolved_by": "user-uuid",11 "resolved_at": "2026-08-22T07:45:00Z",12 "resolved_by_user": {13 "id": "user-uuid",14 "full_name": "Warehouse Supervisor"15 }16 }17}