Skip to main content

Waybills

28 API calls in this section.

Validate Warehouse Waybill Items​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/validate-items

Read-only mixed-item preflight for warehouse-to-warehouse Waybill creation. Destination may be omitted when the first compatible explicit or linked Draft delivery can lock it. Raw-only inventory requires an explicit destination. A staged P&D parent is accepted when its outbound child is Draft, or when it is Pending with one completed P&D pick, a complete warehouse reservation, no active assignment, route, or handoff, and no open delivery or warehouse-item exception. An accepted Pending child returns preparation_will_be_superseded: true. Response returns accepted deliveries/items, localized per-item rejections, locked_destination_warehouse_id, requires_new_delivery_defaults, and can_create. Important rejection codes include staged_outbound_leg_already_assigned, staged_outbound_handoff_active, delivery_in_active_route_session, delivery_has_open_exception, warehouse_item_exception_open, and warehouse_preparation_inconsistent.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
origin_warehouse_idstring—
destination_warehouse_idstring—
delivery_idsarray<string>—
inventory_item_idsarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/validate-items" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "origin_warehouse_id": "{{source_warehouse_id}}",6  "destination_warehouse_id": "{{destination_warehouse_id}}",7  "delivery_ids": [8    "{{delivery_id}}"9  ],10  "inventory_item_ids": [11    "{{inventory_item_id}}"12  ]13}'

Replace {{placeholders}} with your own values before sending.

Response examples

No saved response example. Use Try it to inspect a staging response.

Create Warehouse Waybill (Mixed Items)​

Delivery & Last-Mile / Waybills
POST/delivery/waybills

Atomically creates a Draft warehouse-to-warehouse Waybill from compatible deliveries and/or individually tracked inventory. Idempotency-Key is required. Defaults are required only for raw inventory without a reusable linked Draft delivery. When a selected staged P&D parent has an untouched Pending outbound child, the same transaction preserves its old P&D pick as Cancelled/superseded, releases its stock reservation, cancels pending auto-assignment work, restores that outbound child to Draft, and attaches the parent. Any later create failure rolls all of those changes back. Active assignment, route, handoff, custody movement, or an open exception blocks the swap. Sealing later creates the separate Waybill batch picking order; the old per-delivery P&D picking-order row is never reused. Request fields are unchanged.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Idempotency-Keystring

Example: warehouse-waybill-{{$guid}}

Body parameters

ParameterTypeDescription
origin_typestring—
origin_warehouse_idstring—
destination_typestring—
destination_warehouse_idstring—
carrier_typestring—
delivery_idsarray<string>—
inventory_item_idsarray<string>—
new_delivery_defaultsobject—
new_delivery_defaults.customer_idstring—
new_delivery_defaults.delivery_service_idstring—
new_delivery_defaults.payer_typestring—
new_delivery_defaults.payment_methodstring—
Show 4 more body fields

Additional body parameters

ParameterTypeDescription
new_delivery_defaults.payment_method_type_idstring—
new_delivery_defaults.delivery_feenumber—
new_delivery_defaults.cod_amountnumber—
new_delivery_defaults.notesstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --header "Idempotency-Key: warehouse-waybill-${_GUID}" \5  --data-raw '{6  "origin_type": "warehouse",7  "origin_warehouse_id": "{{source_warehouse_id}}",8  "destination_type": "warehouse",9  "destination_warehouse_id": "{{destination_warehouse_id}}",10  "carrier_type": "internal",11  "delivery_ids": [12    "{{delivery_id}}"13  ],14  "inventory_item_ids": [15    "{{inventory_item_id}}"16  ],17  "new_delivery_defaults": {18    "customer_id": "{{customer_id}}",19    "delivery_service_id": "{{delivery_service_id}}",20    "payer_type": "sender",21    "payment_method": "cash",22    "payment_method_type_id": "{{payment_method_type_id}}",23    "delivery_fee": 0,24    "cod_amount": 0,25    "notes": "Created from warehouse inventory"26  }27}'

Replace {{placeholders}} with your own values before sending.

Response examples

No saved response example. Use Try it to inspect a staging response.

Receive Warehouse Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/:id/receive

Destination warehouse receipt. Idempotency-Key is required. The frontend keeps one Waybill receipt action. Quick receipt still sends only { verification_mode: quick }; verified receipt sends one of present, missing, or damaged for each delivery code. Overage is not supported. Unknown or extra delivery codes must be sent as present; the backend skips them and returns them in operation_result.invalid_delivery_codes. Do not send delivery_id, cage_location_id, or a separate overages array. Optional top-level receiving_location_id selects an active bin in the destination warehouse. When omitted, the backend uses that warehouse's active default_receiving_location; if neither exists the complete receipt rolls back with HTTP 409 destination_receiving_location_required. Each logical delivery receives its own destination receiving order with an exact outbound delivery_id, delivery-code external_reference, parcel item IDs, outcome, and execution state. Present parcels enter destination stock and are executable. Damaged parcels enter damaged/quarantine stock and are blocked. Missing parcels create no destination stock and are blocked. The original warehouse receiving order remains historical and returns can_execute: false with execution_block_reason: receiving_order_custody_transferred. Stock selection follows Waybill delivery membership and never depends on is_single_item. Receipt remains atomic and stops the Waybill at received; close remains separate after discrepancies are resolved.

Path parameters

ParameterTypeDescription
idRequiredstring

Example: <waybill_id>

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Idempotency-Keystring

Example: warehouse-receipt-{{$guid}}

Body parameters

ParameterTypeDescription
verification_modestring—
manifest_versionnumber—
receiving_location_idstring—
deliveriesarray<object>—
deliveries.delivery_codestring—
deliveries.outcomestring—
proof_of_deliveryobject—
proof_of_delivery.notesstring—
proof_of_delivery.photo_urlsarray—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${ID}/receive" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --header "Idempotency-Key: warehouse-receipt-${_GUID}" \5  --data-raw '{6  "verification_mode": "verified",7  "manifest_version": 1,8  "receiving_location_id": "{{receiving_location_id}}",9  "deliveries": [10    {11      "delivery_code": "DEL1001",12      "outcome": "present"13    },14    {15      "delivery_code": "DEL1002",16      "outcome": "missing"17    },18    {19      "delivery_code": "DEL1003",20      "outcome": "damaged"21    },22    {23      "delivery_code": "DEL9999",24      "outcome": "present"25    }26  ],27  "proof_of_delivery": {28    "notes": "Receipt verification completed",29    "photo_urls": []30  }31}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill received successfully",4  "data": {5    "id": "{{waybill_id}}",6    "waybill_number": "WB00032",7    "status": "received",8    "destination_type": "warehouse",9    "destination_warehouse_id": "{{destination_warehouse_id}}",10    "operation_result": {11      "verification_mode": "verified",12      "present_count": 1,13      "missing_count": 1,14      "damaged_count": 1,15      "results": [16        {17          "delivery_code": "DEL1001",18          "outcome": "present",19          "success": true20        },21        {22          "delivery_code": "DEL1002",23          "outcome": "missing",24          "success": true25        },26        {27          "delivery_code": "DEL1003",28          "outcome": "damaged",29          "success": true30        }31      ],32      "invalid_delivery_codes": [33        {34          "delivery_code": "DEL9999",35          "outcome": "present",36          "reason": "delivery_not_found"37        }38      ]39    }40  }41}

Resolve Waybill Receipt Discrepancy​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/:id/discrepancies/:discrepancy_id/resolve

Resolves one open Waybill receipt discrepancy after a warehouse-destination Waybill is received. Idempotency-Key and Waybill-management permission are required. Recording missing or damaged during receipt never creates a reverse delivery. For a missing parcel, send found (with an active destination receiving location) or lost. found uses the parcel's destination receiving order as the receipt source of truth: it creates or updates destination-bin stock, records one idempotent receive inventory movement against that receiving order, changes the receipt outcome to present, makes the receiving order executable, and increases summary.return_breakdown.reached_destination. It does not depend on the origin stock-exit movement type and does not create a reverse delivery. lost closes the discrepancy as a terminal transport loss and creates no reverse delivery because no parcel exists to move. Because lost makes the actual delivery returned, the existing delivery-service return-refund policy is settled atomically and idempotently for that delivery. For a damaged parcel, record the merchant's decision in non-empty merchant_response, then send continue_delivery to change only that parcel's destination stock from damaged to available, change its current receipt outcome to present while retaining the original damage audit, and unlock its receiving order; it does not create a reverse delivery. Only return_to_sender creates the existing no-fee reverse delivery and returns both data.operation_result.return_delivery_id and data.operation_result.return_delivery_code; that physical reverse delivery does not refund the still-active original delivery. Every successful resolution sets receipt_summary.discrepancies to the remaining open discrepancy count. Its generic timeline event uses the selected action as event_type: found, lost, continue_delivery, or return_to_sender. reason is reserved for a separate human explanation and is omitted when none was supplied; notes remains staff comments. Event metadata includes event_category=waybill_discrepancy_resolution, discrepancy_type, resolution, discrepancy_id, delivery_id, and waybill_id. Matching historical events are normalized without deleting their audit history. Missing and damaged parcels remain blocked from normal assignment, status, and return actions while their discrepancy is open. No separate write-off or parallel refund implementation is created by this endpoint. Important HTTP 409 codes include exception_not_found, exception_already_resolved, waybill_missing_resolution_not_allowed, waybill_damaged_resolution_not_allowed, receiving_order_not_found, destination_receiving_location_required, invalid_destination_receiving_location, and no_received_items_to_execute; missing merchant_response returns HTTP 400 merchant_response_required.

Path parameters

ParameterTypeDescription
idRequiredstring

Example: <waybill_id>

discrepancy_idRequiredstring

Example: <receipt_discrepancy_id>

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Idempotency-Keystring

Example: waybill-receipt-resolution-{{$guid}}

Body parameters

ParameterTypeDescription
resolutionstring—
merchant_responsestring—
notesstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${ID}/discrepancies/${DISCREPANCY_ID}/resolve" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --header "Idempotency-Key: waybill-receipt-resolution-${_GUID}" \5  --data-raw '{6  "resolution": "continue_delivery",7  "merchant_response": "Merchant approved delivery despite the recorded damage",8  "notes": "Outer carton dented; contents inspected"9}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Operation completed successfully",4  "data": {5    "id": "{{waybill_id}}",6    "status": "received",7    "receipt_summary": {8      "present": 2,9      "missing": 0,10      "resolved_missing": 1,11      "discrepancies": 0,12      "requires_reconciliation": false13    },14    "operation_result": {15      "success": true,16      "waybill_id": "{{waybill_id}}",17      "discrepancy_id": "{{missing_discrepancy_id}}",18      "delivery_id": "{{missing_delivery_id}}",19      "resolution": "found",20      "delivery_status": "staged_at_warehouse",21      "receiving_order_id": "{{receiving_order_id}}",22      "receiving_location_id": "{{receiving_location_id}}",23      "stock_row_count": 1,24      "stock_quantity": 1,25      "can_execute": true,26      "idempotent_replay": false27    }28  }29}

List Waybills​

Delivery & Last-Mile / Waybills
GET/delivery/waybills

List waybills with pagination and filters for dispatcher/admin screens. Supports warehouse_id + direction filters for warehouse-centric views. Response includes a statuses matrix for tab counts; it respects the active non-status filters and ignores only the current status filter. item_count is the current attached count for open Waybills and the preserved historical non-removed membership count for closed or cancelled Waybills. Response includes a top-level has_empty_drafts boolean; it is tenant-wide and not affected by pagination or filters, so the frontend can decide whether to show empty-draft cleanup actions.

Query parameters

ParameterTypeDescription
pagestring

Page number

Example: 1

limitstring

Items per page (max 100)

Example: 20

statusstring

Filter by status: drafted, sealed, assigned, picked_up, in_transit, awaiting_receipt, received, closed, cancelled

carrier_typestring

Filter by carrier: internal, prime, external

driver_idstring

Filter by assigned driver UUID

from_datestring

Start date (YYYY-MM-DD)

to_datestring

End date (YYYY-MM-DD)

waybill_numberstring

Search by waybill number

warehouse_idstring

Filter by warehouse UUID — shows waybills where this warehouse is origin or destination (combine with direction for specificity)

directionstring

Used with warehouse_id. 'outbound' = origin warehouse, 'inbound' = destination warehouse. Omit to match both.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills?page=1&limit=20&status=&carrier_type=&driver_id=&from_date=&to_date=&waybill_number=&warehouse_id=&direction=" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybills retrieved successfully",4  "data": [5    {6      "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",7      "tenant_id": "{{tenant_id}}",8      "waybill_number": "WB00001",9      "origin_type": "warehouse",10      "origin_warehouse_id": "{{warehouse_id}}",11      "origin_location": {12        "id": "{{warehouse_address_id}}",13        "full_address": "Main Warehouse, Erbil",14        "latitude": 36.191,15        "longitude": 44.01,16        "city": "Erbil",17        "state": null,18        "country": "Iraq",19        "postal_code": null,20        "country_code": "IQ",21        "contact_info": null22      },23      "destination_type": "location",24      "destination_warehouse_id": null,25      "destination_location": {26        "latitude": 36.1901,27        "longitude": 44.0091,28        "full_address": "Erbil, Kurdistan Region"29      },30      "carrier_type": "internal",31      "driver_id": "{{driver_id}}",32      "vehicle_id": "{{vehicle_id}}",33      "external_carrier_ref": null,34      "assigned_by": "{{user_id}}",35      "assignment_time": "2026-04-07T12:20:00Z",36      "status": "sealed",37      "sealed_at": "2026-04-07T12:15:00Z",38      "dispatched_at": null,39      "received_at": null,40      "received_by": null,41      "closed_at": null,42      "cancelled_at": null,43      "proof_of_delivery": null,44      "notes": "Batch for afternoon dispatch",45      "created_by": "{{user_id}}",46      "created_at": "2026-04-07T12:00:00Z",47      "updated_at": "2026-04-07T12:20:00Z",48      "item_count": 1,49      "origin_warehouse": {50        "id": "{{warehouse_id}}",51        "name": "Main Warehouse",52        "code": "WH-MAIN"53      },54      "destination_warehouse": null,55      "origin_zone": {56        "id": "{{origin_zone_id}}",57        "name": "Erbil Central",58        "code": "ERB-CENTRAL"59      },60      "destination_zone": {61        "id": "{{destination_zone_id}}",62        "name": "Erbil East",63        "code": "ERB-EAST"64      }65    }66  ],67  "statuses": [68    {69      "status": "drafted",70      "label": "Drafted",71      "count": 072    },73    {74      "status": "sealed",75      "label": "Sealed",76      "count": 177    },78    {79      "status": "assigned",80      "label": "Assigned",81      "count": 082    },83    {84      "status": "picked_up",85      "label": "Picked Up",86      "count": 087    },88    {89      "status": "in_transit",90      "label": "In Transit",91      "count": 092    },93    {94      "status": "awaiting_receipt",95      "label": "Awaiting Receipt",96      "count": 097    },98    {99      "status": "received",100      "label": "Received",101      "count": 0102    },103    {104      "status": "closed",105      "label": "Closed",106      "count": 0107    },108    {109      "status": "cancelled",110      "label": "Cancelled",111      "count": 0112    }113  ],114  "has_empty_drafts": true,115  "total": 1,116  "page": 1,117  "limit": 20118}

Create Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills

Wallet Error Contract - Waybill Creation

when attached inventory-item delivery creation reaches a known wallet refusal, the response preserves that wallet code and its localized message instead of reporting waybill_service_unavailable. The request and HTTP status behavior are unchanged. The normal non-warehouse-batch create path now returns a compact authoritative Waybill so the caller does not need an immediate detail reload; the separately documented atomic warehouse-batch create flow keeps its existing response.

Create a draft waybill. You can attach deliveries at creation time or later while still drafted. A selected staged P&D parent may have an untouched Pending outbound child. In that case, creation atomically cancels the old per-delivery P&D pick as superseded, releases its reservation and pending auto-assignment, returns the outbound child to Draft, and attaches the parent. The old pick remains audit history and is not reused; sealing creates the Waybill batch pick. Active assignment, route, handoff, custody movement, or an open exception blocks the change.

The backend generates waybill_number atomically in PostgreSQL. New delivery Waybills use the format WB00001 without a hyphen; existing WB-00001 values remain valid and are not rewritten. Origin and destination warehouse validation is batched, and independent zone lookups run in parallel.

Frontend contract

  • The request payload is unchanged. Do not send waybill_number; it is server-generated.

  • On success, use data.id as the entity ID and display data.waybill_number exactly as returned.

  • The normal non-warehouse-batch success payload is a compact authoritative Waybill with item_count, summary, endpoint warehouse/location/zone context and, when deliveries were requested, operation_result. It intentionally omits embedded deliveries, receipt detail, discrepancies and timeline data. The separately documented atomic warehouse-batch create flow is unchanged. Use GET /delivery/waybills/{id} only when the detail screen needs those fields.

  • On errors, use the top-level code for behavior and the localized message for display.

  • Business and eligibility failures return localized 400, 404, or 409 responses. When details is an item array, each item includes its stable code and localized message.

  • The create endpoint does not return HTTP 500. Database or unexpected availability failures return waybill_service_unavailable with HTTP 503 and details.operation_id; preserve the form so the user can retry and show the operation ID when contacting support.

  • Important creation codes: invalid_request_body (400), waybill_origin_warehouse_not_found or waybill_destination_warehouse_not_found (404), staged_outbound_leg_already_assigned, staged_outbound_handoff_active, delivery_in_active_route_session, delivery_has_open_exception, warehouse_item_exception_open, warehouse_preparation_inconsistent, waybill_items_rejected, or waybill_number_conflict (409), and waybill_service_unavailable (503).

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
origin_typestring—
origin_warehouse_idstring—
destination_typestring—
destination_locationobject—
destination_location.latitudenumber—
destination_location.longitudenumber—
destination_location.full_addressstring—
carrier_typestring—
notesstring—
delivery_idsarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "origin_type": "warehouse",6  "origin_warehouse_id": "{{warehouse_id}}",7  "destination_type": "location",8  "destination_location": {9    "latitude": 36.1901,10    "longitude": 44.0091,11    "full_address": "Erbil, Kurdistan Region"12  },13  "carrier_type": "internal",14  "notes": "Batch for afternoon dispatch",15  "delivery_ids": [16    "{{delivery_id}}"17  ]18}'

Replace {{placeholders}} with your own values before sending.

Response examples

201Created
Response body
json
1{2  "success": true,3  "message": "Waybill created successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": {11      "id": "{{warehouse_address_id}}",12      "full_address": "Main Warehouse, Erbil",13      "latitude": 36.191,14      "longitude": 44.0115    },16    "destination_type": "location",17    "destination_warehouse_id": null,18    "destination_location": {19      "latitude": 36.1901,20      "longitude": 44.0091,21      "full_address": "Erbil, Kurdistan Region"22    },23    "status": "drafted",24    "item_count": 1,25    "summary": {26      "total_deliveries": 1,27      "total_unit_size": 1,28      "status_breakdown": {29        "pending": 130      }31    },32    "operation_result": {33      "attached": 1,34      "attached_delivery_ids": [35        "{{delivery_id}}"36      ],37      "superseded_delivery_ids": [],38      "superseded_picking_order_ids": []39    }40  }41}

Get Waybill Detail​

Delivery & Last-Mile / Waybills
GET/delivery/waybills/{{waybill_id}}

Returns enriched Waybill detail. received_by is the receiver user ID and received_by_user is the matching user object (id, full_name, email, phone, avatar_url); both are null until the Waybill is received.

Summary contract

summary.return_breakdown.reached_destination is always 0 before the Waybill reaches received. For a received or closed warehouse-destination Waybill, it counts the authoritative receipt outcomes present and damaged; an unresolved missing parcel is not counted even when that delivery still has a historical staged_at_warehouse status from the origin. Resolving the parcel as found changes its receipt item to present, so it starts counting as reached. Resolving it as lost keeps the internal terminal delivery status used by the operational and finance flow, but reporting counts it only in summary.return_breakdown.lost, never in returned; its embedded delivery has is_lost=true and is_returned=false. For non-warehouse destinations, the existing delivered/staged status rule remains. A requested, approved, or in-transit return does not count as returned. Only a physically completed return counts in returned; completed returns are excluded from reached_destination, and their embedded delivery has is_returned=true. reached_destination, returned, lost, cancelled, and active are mutually exclusive sibling counts and reconcile to summary.total_deliveries; fully_returned is true only when every child was physically returned.

Receipt count contract

receipt_summary.missing, receipt_summary.damaged, and receipt_summary.discrepancies count only currently open discrepancies. receipt_summary.resolved_missing and receipt_summary.resolved_damaged retain historical resolved counts. requires_reconciliation remains true while any delivery discrepancy is open.

Discrepancy detail contract

discrepancies.present[] contains delivery_code and barcode plus the internal delivery_id. discrepancies.missing[] keeps both open and resolved missing history and includes resolution, resolution_notes, resolution_details, and available_resolutions. available_resolutions is [found, lost] only while that missing discrepancy is open; otherwise it is empty. Missing and damaged rows also contain delivery_code, barcode, the raw stored reason, notes, status, reported_by_user, resolved_by_user, created_at, and resolved_at, while retaining internal IDs. User objects contain id, full_name, email, phone, and avatar_url. The canonical resolver property is resolved_by_user; resolve_by_user is never returned. Reasons are stable raw backend values such as missing_on_receive and damaged_on_receive; clients must not translate or reformat them. Dates are ISO 8601 timestamps. Open discrepancies may have null resolved_by_user and resolved_at. available_actions contains resolve_missing_discrepancy when a received Waybill has at least one open missing discrepancy. Frontend display must use delivery_code || barcode and must not depend on delivery IDs.

Historical receipts without an item-level receipt audit use the existing compatibility rule: manifest deliveries without an open missing receipt discrepancy count as reached; damaged deliveries count as physically reached. Before receipt, discrepancies.present is an empty array. status_updates is the chronological audit timeline for lifecycle, custody, and discrepancy resolution actions. Discrepancy resolution events use the selected action as event_type: found, lost, continue_delivery, or return_to_sender. reason is optional human context only. Each event contains event_type, previous/new status where applicable, timestamp, actor, optional reason/notes, and metadata.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill retrieved successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "status": "picked_up",9    "driver_id": "{{new_driver_id}}",10    "assignment_time": "2026-04-07T12:35:00Z",11    "status_updates": [12      {13        "event_type": "created",14        "previous_status": null,15        "new_status": "drafted",16        "timestamp": "2026-04-07T12:00:00Z",17        "actor_id": "{{user_id}}",18        "actor_role": "dispatcher",19        "actor": {20          "id": "{{user_id}}",21          "full_name": "Dispatcher User"22        },23        "metadata": {24          "previous_driver": null,25          "new_driver": null,26          "previous_vehicle": null,27          "new_vehicle": null28        }29      },30      {31        "event_type": "driver_switched",32        "previous_status": "picked_up",33        "new_status": "picked_up",34        "timestamp": "2026-04-07T12:35:00Z",35        "actor_id": "{{user_id}}",36        "actor_role": "dispatcher",37        "reason": "Previous driver had vehicle issue",38        "actor": {39          "id": "{{user_id}}",40          "full_name": "Dispatcher User"41        },42        "metadata": {43          "previous_driver_id": "{{driver_id}}",44          "new_driver_id": "{{new_driver_id}}",45          "previous_vehicle_id": "{{vehicle_id}}",46          "new_vehicle_id": "{{new_vehicle_id}}",47          "previous_driver": {48            "id": "{{driver_id}}",49            "user": {50              "full_name": "John Driver"51            }52          },53          "new_driver": {54            "id": "{{new_driver_id}}",55            "user": {56              "full_name": "Backup Driver"57            }58          },59          "previous_vehicle": {60            "id": "{{vehicle_id}}",61            "name": "Van 101"62          },63          "new_vehicle": {64            "id": "{{new_vehicle_id}}",65            "name": "Van 202"66          }67        }68      },69      {70        "event_type": "in_transit",71        "previous_status": "picked_up",72        "new_status": "in_transit",73        "timestamp": "2026-04-07T12:45:00Z",74        "actor_role": "driver",75        "actor": {76          "id": "{{driver_user_id}}",77          "full_name": "Backup Driver"78        },79        "metadata": {80          "previous_driver": null,81          "new_driver": null,82          "previous_vehicle": null,83          "new_vehicle": null84        }85      }86    ]87  }88}

List Waybill Deliveries​

Delivery & Last-Mile / Waybills
GET/delivery/waybills/{{waybill_id}}/deliveries

Returns the durable Waybill manifest. A current deliveries.waybill_id link and the latest non-removed delivery_waybill_history record both establish membership, so a delivery remains listed after a terminal lost/returned flow clears its live link. A delivery explicitly removed while membership is editable is excluded. total, filtering, sorting, and pagination use this same manifest population. Each row exposes membership_outcome, return_status, is_returned, and is_lost. is_lost is true only for a resolved Missing → Lost discrepancy. is_returned is false for Lost and becomes true only after physical return completion or for a legacy terminal returned record without a Lost discrepancy. returned_at remains null for Lost and while a physical return is only requested, approved, or in transit. Membership is not implicitly restricted to the Waybill origin warehouse, so legacy rows remain visible when their warehouse fields are null. Send warehouse_id only when intentionally filtering the manifest by warehouse.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Query parameters

ParameterTypeDescription
pagestring

Page number, minimum 1

Example: 1

limitstring

Items per page, from 1 to 100

Example: 20

sort_bystring

delivery_code, status, barcode, external_reference_id, unit_size, created_at, or customer

Example: created_at

sort_orderstring

asc or desc

Example: desc

searchstring

Search delivery code, barcode, external reference, or customer name

warehouse_idOptionalstring

Optional pickup or dropoff warehouse UUID

column_filtersstring

URL-encoded JSON. Supports delivery_code, status, barcode, external_reference_id, pickup, delivery, unit_size, payment_method, cod_amount, created_at, and customer. pickup matches address or pickup zone; delivery matches address or dropoff zone. unit_size and cod_amount accept an exact value or {"min":value,"max":value}.

Example: {}

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/deliveries?page=1&limit=20&sort_by=created_at&sort_order=desc&search=&column_filters={}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill deliveries retrieved successfully",4  "data": [5    {6      "id": "{{delivery_id}}",7      "delivery_code": "1533",8      "barcode": "DEL1533",9      "external_reference_id": "ORD-1533",10      "status": "delivered",11      "return_status": "return_approved",12      "membership_outcome": "active",13      "pickup_location": {14        "full_address": "Origin warehouse",15        "latitude": 36.191,16        "longitude": 44.0117      },18      "delivery_location": {19        "full_address": "Customer address",20        "latitude": 36.1901,21        "longitude": 44.009122      },23      "unit_size": 1,24      "payment_method": "cod",25      "cod_amount": 12000,26      "notes": "Handle with care",27      "created_at": "2026-04-07T11:40:00Z",28      "pickup_zone_id": "{{origin_zone_id}}",29      "dropoff_zone_id": "{{destination_zone_id}}",30      "pickup_warehouse_id": "{{warehouse_id}}",31      "dropoff_warehouse_id": null,32      "is_returned": false,33      "return_reason": "damaged",34      "return_trigger_point": "rejected_at_destination",35      "returned_at": null,36      "pickup_warehouse": {37        "id": "{{warehouse_id}}",38        "name": "Main Warehouse",39        "code": "WH-MAIN"40      },41      "dropoff_warehouse": null,42      "pickup_zone": {43        "id": "{{origin_zone_id}}",44        "name": "Central Pickup Zone",45        "code": "PZ-CENTRAL"46      },47      "dropoff_zone": {48        "id": "{{destination_zone_id}}",49        "name": "Downtown Dropoff Zone",50        "code": "DZ-DOWNTOWN"51      },52      "customer": {53        "id": "{{customer_id}}",54        "full_name": "Customer Name",55        "avatar_url": null56      }57    }58  ],59  "total": 1,60  "page": 1,61  "limit": 2062}

Update Waybill​

Delivery & Last-Mile / Waybills
PUT/delivery/waybills/{{waybill_id}}

Update a drafted waybill only.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
notesstring—
carrier_typestring—
Staging
Request
curl
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "notes": "Updated notes",6  "carrier_type": "internal"7}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill updated successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": {11      "id": "{{warehouse_address_id}}",12      "full_address": "Main Warehouse, Erbil",13      "latitude": 36.191,14      "longitude": 44.01,15      "city": "Erbil",16      "state": null,17      "country": "Iraq",18      "postal_code": null,19      "country_code": "IQ",20      "contact_info": null21    },22    "destination_type": "location",23    "destination_warehouse_id": null,24    "destination_location": {25      "latitude": 36.1901,26      "longitude": 44.0091,27      "full_address": "Erbil, Kurdistan Region"28    },29    "carrier_type": "internal",30    "driver_id": "{{driver_id}}",31    "vehicle_id": "{{vehicle_id}}",32    "external_carrier_ref": null,33    "assigned_by": "{{user_id}}",34    "assignment_time": "2026-04-07T12:20:00Z",35    "status": "sealed",36    "sealed_at": "2026-04-07T12:15:00Z",37    "dispatched_at": null,38    "received_at": null,39    "received_by": null,40    "closed_at": null,41    "cancelled_at": null,42    "proof_of_delivery": null,43    "notes": "Updated notes",44    "created_by": "{{user_id}}",45    "created_at": "2026-04-07T12:00:00Z",46    "updated_at": "2026-04-07T12:20:00Z"47  }48}

Scan Eligible Delivery for Waybill​

Delivery & Last-Mile / Waybills
GET/delivery/waybills/{{waybill_id}}/scan/{{scan_value}}

Validate one scanned delivery without attaching it to the waybill. The path value is matched exactly in this order: barcode, external_reference_id, then delivery_code (with an optional DEL prefix). Success returns the same delivery field shape used in waybill details, including locations, payment, zones, warehouses, and return fields. A staged P&D parent is scannable with a Draft outbound child or an untouched Pending outbound preparation; attaching the latter atomically supersedes its old P&D pick and reservation. The frontend must add data.id to shared local selection state, handle duplicate scans locally, and submit all selected IDs once through POST /delivery/waybills/{id}/deliveries. Important errors return a machine-readable code: delivery_not_found (404), delivery_already_in_waybill, delivery_in_active_route_session, delivery_parent_not_eligible, delivery_status_not_eligible, delivery_has_active_assignment, staged_outbound_handoff_active, warehouse_preparation_inconsistent, or waybill_membership_locked (409).

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

scan_valueRequiredstring

URL-encoded barcode, external_reference_id, or delivery_code

Example: DEL1533

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/scan/${SCAN_VALUE}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Eligible delivery retrieved successfully",4  "data": {5    "id": "3bebb14a-3179-4b34-8a65-6e52afa2870f",6    "delivery_code": "3033",7    "barcode": "DEL3033",8    "external_reference_id": "",9    "status": "pending",10    "pickup_location": {11      "id": "c8eed194-887e-4da9-b312-2d04d8edc4d8",12      "city": "Baghdad",13      "state": null,14      "country": "Iraq",15      "latitude": 33.29605487,16      "longitude": 44.3399507,17      "postal_code": null,18      "contact_info": null,19      "country_code": null,20      "full_address": "1234, Yarmouk, Baghdad, Baghdad Governorate, Iraq"21    },22    "delivery_location": {23      "id": "03fc2031-1778-4355-874f-4589287f3cde",24      "city": "Lajan",25      "state": null,26      "country": "Iraq",27      "latitude": 36.125664,28      "longitude": 43.79169851,29      "postal_code": null,30      "contact_info": null,31      "country_code": null,32      "full_address": "206, lajan, Lajan, Erbil Governorate, Iraq"33    },34    "unit_size": 1,35    "payment_method": "cash",36    "cod_amount": 10000,37    "notes": "",38    "created_at": "2026-05-22T12:33:54.61787+00:00",39    "pickup_zone_id": "23ba845d-faf0-4241-ad50-86a438d98853",40    "dropoff_zone_id": "4f04820c-e9de-4449-ad12-2cd3758ac09b",41    "pickup_warehouse_id": "3a577909-49a2-4819-a54b-0559e7dec3ce",42    "dropoff_warehouse_id": "8e2f5943-8500-49cc-8830-f12a3fc40d4e",43    "is_returned": false,44    "return_reason": null,45    "return_trigger_point": null,46    "returned_at": null47  }48}

List Eligible Deliveries for Waybill​

Delivery & Last-Mile / Waybills
GET/delivery/waybills/{{waybill_id}}/eligible-deliveries

Returns deliveries that can be attached to the target drafted Waybill. Direct outbound Draft/Pending deliveries keep their existing eligibility. A staged P&D parent is also included when its outbound child is Draft, or Pending with one completed P&D pick and full reservation, no assignment, route, handoff, custody movement, or open exception. The backend resolves that derived eligibility in one set-based database query before filtering, counting, sorting, and pagination. The response includes customer information. Frontend sends column_filters as URL-encoded JSON and handles 400 validation errors for invalid JSON, keys, value types, sort fields, or date values; 409 means Waybill membership is locked.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Query parameters

ParameterTypeDescription
pagestring

Page number, minimum 1

Example: 1

limitstring

Items per page, from 1 to 100

Example: 25

sort_bystring

Sort field: delivery_code, status, barcode, external_reference_id, unit_size, created_at, or customer

Example: created_at

sort_orderstring

Sort direction: asc or desc

Example: desc

searchstring

Global partial search by delivery code, barcode, external reference, or customer name

warehouse_idOptionalstring

Optional warehouse UUID. Matches pickup or dropoff warehouse.

column_filtersstring

URL-encoded JSON. Supports delivery_code, status, barcode, external_reference_id, pickup, delivery, unit_size, payment_method, cod_amount, created_at, and customer. pickup matches address or pickup zone; delivery matches address or dropoff zone. unit_size and cod_amount accept an exact value or {"min":value,"max":value}.

Example: {}

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/eligible-deliveries?page=1&limit=25&sort_by=created_at&sort_order=desc&search=&column_filters={}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Eligible deliveries retrieved successfully",4  "data": [5    {6      "id": "{{delivery_id}}",7      "delivery_code": "1533",8      "barcode": "DEL1533",9      "external_reference_id": "ORD-1533",10      "status": "pending",11      "unit_size": 1,12      "payment_method": "cod",13      "cod_amount": 12000,14      "notes": "Handle with care",15      "pickup_location": {16        "full_address": "Origin warehouse",17        "latitude": 36.191,18        "longitude": 44.0119      },20      "delivery_location": {21        "full_address": "Customer address",22        "latitude": 36.1901,23        "longitude": 44.009124      },25      "pickup_warehouse_id": "{{warehouse_id}}",26      "dropoff_warehouse_id": null,27      "pickup_warehouse": {28        "id": "{{warehouse_id}}",29        "name": "Main Warehouse",30        "code": "WH-MAIN"31      },32      "dropoff_warehouse": null,33      "pickup_zone": {34        "id": "{{pickup_zone_id}}",35        "name": "Central Pickup Zone",36        "code": "PZ-CENTRAL"37      },38      "dropoff_zone": {39        "id": "{{dropoff_zone_id}}",40        "name": "Downtown Dropoff Zone",41        "code": "DZ-DOWNTOWN"42      },43      "customer": {44        "id": "{{customer_id}}",45        "full_name": "Customer Name",46        "avatar_url": null47      },48      "created_at": "2026-04-07T11:40:00Z",49      "updated_at": "2026-04-07T11:45:00Z"50    }51  ],52  "total": 1,53  "page": 1,54  "limit": 2555}

Add Deliveries to Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/deliveries

Attach selected deliveries to a drafted Waybill. Use the eligible-deliveries endpoint first so the dispatcher sees only addable deliveries. A staged P&D parent may have a Draft outbound child or an untouched Pending outbound preparation. For Pending, attachment preserves the existing P&D picking order as Cancelled/superseded, releases its stock reservation, cancels pending auto-assignment, restores the outbound child to Draft, and attaches the parent in the same transaction. It never reuses that per-delivery pick: Waybill seal creates the single batch picking order. Active assignment, route, handoff, custody movement, or an open exception returns a localized 409 conflict.

For an eligible warehouse-to-warehouse Waybill, the first delivery attachment also creates the Waybill package-transfer projection in the same transaction. After success, the Waybill is discoverable through the source warehouse's Outgoing Package Transfers list and the destination warehouse's Incoming list. Empty Waybills, same-warehouse routes, and routes without a valid warehouse destination do not create that projection.

The database performs validation, attachment, transfer projection, summary calculation and realtime item-count synchronization atomically. The success payload is a compact authoritative Waybill with item_count, summary, endpoint context and operation_result.attached plus operation_result.attached_delivery_ids; it intentionally omits embedded delivery and timeline detail.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
delivery_idsarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/deliveries" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "delivery_ids": [6    "{{delivery_id}}"7  ]8}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Deliveries added to waybill successfully",4  "data": {5    "id": "{{waybill_id}}",6    "waybill_number": "WB00001",7    "status": "drafted",8    "item_count": 2,9    "summary": {10      "total_deliveries": 2,11      "total_unit_size": 2,12      "status_breakdown": {13        "pending": 214      }15    },16    "operation_result": {17      "attached": 1,18      "attached_delivery_ids": [19        "{{delivery_id}}"20      ],21      "superseded_delivery_ids": [],22      "superseded_picking_order_ids": []23    }24  }25}

Remove Delivery from Waybill​

Delivery & Last-Mile / Waybills
DELETE/delivery/waybills/{{waybill_id}}/deliveries/{{delivery_id}}

Atomically remove one delivery from a drafted Waybill. The success payload is the compact authoritative Waybill with updated item_count, summary, endpoint context and operation_result.removed_delivery_id; it intentionally omits embedded delivery and timeline detail.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

delivery_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/deliveries/${DELIVERY_ID}" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Delivery removed from waybill successfully",4  "data": {5    "id": "{{waybill_id}}",6    "waybill_number": "WB00001",7    "status": "drafted",8    "item_count": 1,9    "summary": {10      "total_deliveries": 1,11      "total_unit_size": 1,12      "status_breakdown": {13        "pending": 114      }15    },16    "operation_result": {17      "removed_delivery_id": "{{delivery_id}}"18    }19  }20}

Seal Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/seal

Seal a drafted waybill. After this, membership is locked and no deliveries can be added/removed. For a warehouse origin, sealing creates or reuses the pending picking order; stock remains in the origin warehouse until physical carrier handoff.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/seal" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill sealed successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "internal",19    "driver_id": "{{driver_id}}",20    "vehicle_id": "{{vehicle_id}}",21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:20:00Z",24    "status": "sealed",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": null,27    "received_at": null,28    "received_by": "{{receiver_user_id}}",29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:20:00Z",36    "driver": {37      "id": "{{driver_id}}",38      "status": "active",39      "user": {40        "id": "{{driver_user_id}}",41        "full_name": "John Driver",42        "email": "driver@example.com",43        "phone": "+9647500000000",44        "avatar_url": "https://example.com/driver.jpg"45      }46    },47    "vehicle": {48      "id": "{{vehicle_id}}",49      "name": "Van 101",50      "code": "VAN-101",51      "capacity_unit": 50,52      "image_url": "https://example.com/vehicles/van-101.jpg"53    },54    "origin_warehouse": {55      "id": "{{warehouse_id}}",56      "name": "Main Warehouse",57      "code": "WH-MAIN"58    },59    "destination_warehouse": null,60    "origin_zone": {61      "id": "{{origin_zone_id}}",62      "name": "Erbil Central",63      "code": "ERB-CENTRAL"64    },65    "destination_zone": {66      "id": "{{destination_zone_id}}",67      "name": "Erbil East",68      "code": "ERB-EAST"69    },70    "assigned_by_user": {71      "id": "{{user_id}}",72      "full_name": "Dispatcher User",73      "email": "dispatcher@example.com",74      "phone": "+9647511111111",75      "avatar_url": "https://example.com/users/dispatcher.jpg"76    },77    "received_by_user": {78      "id": "{{receiver_user_id}}",79      "full_name": "Warehouse Receiver",80      "email": "receiver@example.com",81      "phone": "+9647522222222",82      "avatar_url": "https://example.com/users/receiver.jpg"83    },84    "total_unit_size": 1,85    "deliveries": [86      {87        "id": "{{delivery_id}}",88        "delivery_code": 1533,89        "barcode": "DEL1533",90        "external_reference_id": "ORD-1533",91        "status": "pending",92        "pickup_location": {93          "full_address": "Origin warehouse",94          "latitude": 36.191,95          "longitude": 44.0196        },97        "delivery_location": {98          "full_address": "Customer address",99          "latitude": 36.1901,100          "longitude": 44.0091101        },102        "unit_size": 1,103        "payment_method": "cod",104        "cod_amount": 12000,105        "notes": "Handle with care",106        "created_at": "2026-04-07T11:40:00Z",107        "pickup_zone_id": "{{origin_zone_id}}",108        "dropoff_zone_id": "{{destination_zone_id}}",109        "pickup_warehouse_id": "{{warehouse_id}}",110        "dropoff_warehouse_id": null111      }112    ],113    "summary": {114      "total_deliveries": 1,115      "total_unit_size": 1,116      "status_breakdown": {117        "pending": 1118      }119    }120  }121}

Assign Driver to Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/assign

Assign an internal sealed waybill to a driver using the driver primary id from drivers.id. Backend derives the assigned vehicle from the driver's primary vehicle assignment, stores both ids on the waybill, changes the waybill status to assigned, marks pending child deliveries as assigned, and marks child delivery assignment rows as assigned immediately. Waybills do not use the normal delivery accept/reject gate, so the driver can pick up the Waybill after assignment.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
driver_idstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/assign" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "driver_id": "{{driver_id}}"6}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill assigned to driver successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "internal",19    "driver_id": "{{driver_id}}",20    "vehicle_id": "{{vehicle_id}}",21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:20:00Z",24    "status": "assigned",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": null,27    "received_at": null,28    "received_by": null,29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:20:00Z"36  }37}

Switch Waybill Driver​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/switch-driver

Creates one Waybill-level waybill_driver_switched notification per affected driver, never one per child delivery. Before pickup the previous driver receives a critical acknowledgement modal and the replacement receives a normal notification. After pickup/in transit the previous driver receives a critical modal with replacement-driver contact and coordination instructions; the replacement receives an important banner with previous-driver contact. Phone/avatar are nullable and copy/call actions are omitted without phone. Notification work is backgrounded.

Switch the assigned driver of an internal waybill. reason is mandatory, must be 1-500 characters, and is persisted in the driver_switched timeline event. Allowed in assigned, picked_up, and in_transit for dispatcher custody transfer. Backend derives the new vehicle from the new driver's primary vehicle assignment and records the previous/new driver and vehicle snapshots.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
driver_idstring—
reasonstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/switch-driver" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "driver_id": "{{new_driver_id}}",6  "reason": "Previous driver had vehicle issue"7}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill driver switched successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "internal",19    "driver_id": "{{new_driver_id}}",20    "vehicle_id": "{{new_vehicle_id}}",21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:35:00Z",24    "status": "picked_up",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": "2026-04-07T12:30:00Z",27    "received_at": null,28    "received_by": null,29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:35:00Z",36    "driver": {37      "id": "{{new_driver_id}}",38      "status": "active",39      "user": {40        "id": "{{new_driver_user_id}}",41        "full_name": "Backup Driver",42        "email": "backup.driver@example.com",43        "phone": "+9647500000001",44        "avatar_url": "https://example.com/driver2.jpg"45      }46    },47    "vehicle": {48      "id": "{{new_vehicle_id}}",49      "name": "Van 202",50      "code": "VAN-202",51      "capacity_unit": 50,52      "image_url": "https://example.com/vehicles/van-202.jpg"53    },54    "origin_warehouse": {55      "id": "{{warehouse_id}}",56      "name": "Main Warehouse",57      "code": "WH-MAIN"58    },59    "destination_warehouse": null,60    "origin_zone": {61      "id": "{{origin_zone_id}}",62      "name": "Erbil Central",63      "code": "ERB-CENTRAL"64    },65    "destination_zone": {66      "id": "{{destination_zone_id}}",67      "name": "Erbil East",68      "code": "ERB-EAST"69    },70    "assigned_by_user": {71      "id": "{{user_id}}",72      "full_name": "Dispatcher User",73      "email": "dispatcher@example.com",74      "phone": "+9647511111111",75      "avatar_url": "https://example.com/users/dispatcher.jpg"76    },77    "received_by_user": null,78    "total_unit_size": 1,79    "deliveries": [80      {81        "id": "{{delivery_id}}",82        "delivery_code": 1533,83        "barcode": "DEL1533",84        "external_reference_id": "ORD-1533",85        "status": "picked_up",86        "pickup_location": {87          "full_address": "Origin warehouse",88          "latitude": 36.191,89          "longitude": 44.0190        },91        "delivery_location": {92          "full_address": "Customer address",93          "latitude": 36.1901,94          "longitude": 44.009195        },96        "unit_size": 1,97        "payment_method": "cod",98        "cod_amount": 12000,99        "notes": "Handle with care",100        "created_at": "2026-04-07T11:40:00Z",101        "pickup_zone_id": "{{origin_zone_id}}",102        "dropoff_zone_id": "{{destination_zone_id}}",103        "pickup_warehouse_id": "{{warehouse_id}}",104        "dropoff_warehouse_id": null105      }106    ],107    "summary": {108      "total_deliveries": 1,109      "total_unit_size": 1,110      "status_breakdown": {111        "picked_up": 1112      }113    }114  }115}

Unassign Waybill Driver​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/unassign

The previous driver receives one Waybill-level waybill_driver_unassigned critical modal requiring acknowledgement. Notification work is backgrounded and does not block the API response.

Unassign the current driver from an internal waybill. Allowed only while the waybill is assigned. After pickup, dispatcher must use switch-driver instead of unassign.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/unassign" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill driver unassigned successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "internal",19    "driver_id": null,20    "vehicle_id": null,21    "external_carrier_ref": null,22    "assigned_by": null,23    "assignment_time": null,24    "status": "sealed",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": null,27    "received_at": null,28    "received_by": null,29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:40:00Z",36    "driver": null,37    "vehicle": null,38    "origin_warehouse": {39      "id": "{{warehouse_id}}",40      "name": "Main Warehouse",41      "code": "WH-MAIN"42    },43    "destination_warehouse": null,44    "origin_zone": {45      "id": "{{origin_zone_id}}",46      "name": "Erbil Central",47      "code": "ERB-CENTRAL"48    },49    "destination_zone": {50      "id": "{{destination_zone_id}}",51      "name": "Erbil East",52      "code": "ERB-EAST"53    },54    "assigned_by_user": null,55    "received_by_user": null,56    "total_unit_size": 1,57    "deliveries": [58      {59        "id": "{{delivery_id}}",60        "delivery_code": 1533,61        "barcode": "DEL1533",62        "external_reference_id": "ORD-1533",63        "status": "pending",64        "pickup_location": {65          "full_address": "Origin warehouse",66          "latitude": 36.191,67          "longitude": 44.0168        },69        "delivery_location": {70          "full_address": "Customer address",71          "latitude": 36.1901,72          "longitude": 44.009173        },74        "unit_size": 1,75        "payment_method": "cod",76        "cod_amount": 12000,77        "notes": "Handle with care",78        "created_at": "2026-04-07T11:40:00Z",79        "pickup_zone_id": "{{origin_zone_id}}",80        "dropoff_zone_id": "{{destination_zone_id}}",81        "pickup_warehouse_id": "{{warehouse_id}}",82        "dropoff_warehouse_id": null83      }84    ],85    "summary": {86      "total_deliveries": 1,87      "total_unit_size": 1,88      "status_breakdown": {89        "pending": 190      }91    }92  }93}

Dispatch Waybill (External Carrier)​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/dispatch

Move a non-internal Waybill from sealed to in_transit. For a warehouse origin, this is the physical carrier handoff boundary: the picking order and outbound handoffs complete, stock is removed from the origin warehouse, and each parcel receives a transfer_out movement. Internal carrier Waybills should use driver-action instead.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/dispatch" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill dispatched successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "external",19    "driver_id": null,20    "vehicle_id": null,21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:20:00Z",24    "status": "in_transit",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": "2026-04-07T12:30:00Z",27    "received_at": null,28    "received_by": null,29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:20:00Z"36  }37}

Receive Waybill (External Carrier, Location Destination)​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/receive

Complete an external/prime carrier Waybill whose destination is a customer/location. proof_of_delivery is optional. Send it only when proof data exists. Warehouse destinations use the delivery-code verified receipt contract documented under Receive Warehouse Waybill. For internal customer/location flow, use driver-action instead.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
proof_of_deliveryobject—
proof_of_delivery.signature_urlstring—
proof_of_delivery.photo_urlsarray<string>—
proof_of_delivery.notesstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/receive" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "proof_of_delivery": {6    "signature_url": "https://example.com/signature.png",7    "photo_urls": [8      "https://example.com/photo1.jpg"9    ],10    "notes": "Received in good condition"11  }12}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill received successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "external",19    "driver_id": null,20    "vehicle_id": null,21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:20:00Z",24    "status": "received",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": null,27    "received_at": "2026-04-07T14:00:00Z",28    "received_by": null,29    "closed_at": null,30    "cancelled_at": null,31    "proof_of_delivery": {32      "signature_url": "https://example.com/signature.png",33      "photo_urls": [34        "https://example.com/photo1.jpg"35      ],36      "notes": "Received in good condition"37    },38    "notes": "Batch for afternoon dispatch",39    "created_by": "{{user_id}}",40    "created_at": "2026-04-07T12:00:00Z",41    "updated_at": "2026-04-07T12:20:00Z"42  }43}

Close Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/close

Close a received Waybill after reconciliation is complete. Warehouse-transfer order history is created earlier, when present/damaged deliveries are marked received. warehouse_transfer_completed is written to the affected delivery's own event history and to its canonical parent's aggregate history; a direct/root member is written once. Close does not add a duplicate because the event is deduplicated by Waybill ID; the deployed close projection remains only as a fallback for legacy rows that reached close without a receipt event. status_updates, delivery/assignment status, custody, capacity, finance, realtime, notifications, and webhooks are unchanged by the order-history projection. HTTP 409 returns waybill_not_closable when the Waybill is not received or waybill_open_discrepancies with details.open_discrepancy_count when receipt discrepancies remain open.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/close" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill closed successfully",4  "data": {5    "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",6    "tenant_id": "{{tenant_id}}",7    "waybill_number": "WB00001",8    "origin_type": "warehouse",9    "origin_warehouse_id": "{{warehouse_id}}",10    "origin_location": null,11    "destination_type": "location",12    "destination_warehouse_id": null,13    "destination_location": {14      "latitude": 36.1901,15      "longitude": 44.0091,16      "full_address": "Erbil, Kurdistan Region"17    },18    "carrier_type": "internal",19    "driver_id": "{{driver_id}}",20    "vehicle_id": "{{vehicle_id}}",21    "external_carrier_ref": null,22    "assigned_by": "{{user_id}}",23    "assignment_time": "2026-04-07T12:20:00Z",24    "status": "closed",25    "sealed_at": "2026-04-07T12:15:00Z",26    "dispatched_at": null,27    "received_at": "2026-04-07T14:00:00Z",28    "received_by": null,29    "closed_at": "2026-04-07T14:15:00Z",30    "cancelled_at": null,31    "proof_of_delivery": null,32    "notes": "Batch for afternoon dispatch",33    "created_by": "{{user_id}}",34    "created_at": "2026-04-07T12:00:00Z",35    "updated_at": "2026-04-07T12:20:00Z"36  }37}

Bulk Delete Empty Draft Waybills​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/bulk-delete-empty-drafts

Permanently delete empty drafted Waybills using exactly one scope.

Selected scope

send waybill_ids with 1-100 IDs.

All-empty scope

send { "all_empty_drafts": true } to delete every Waybill in the authenticated tenant that is exactly drafted and has zero attached deliveries.

Do not send both fields. Missing both returns validation error waybill_bulk_delete_scope_required; sending both returns waybill_bulk_delete_scope_conflict. These messages and the success/conflict responses are localized for English, Arabic, Kurdish, and Persian through the existing request language.

Frontend contract

  • Ask the user to choose selected rows or all empty drafts and require confirmation before the request.

  • Remove only IDs returned in data.deleted_ids.

  • Use data.scope (selected or all_empty_drafts) to confirm which operation ran.

  • For selected scope, keep rows returned in data.skipped and use reason (not_drafted or has_deliveries) for feedback.

  • Treat data.not_found as stale selected rows.

  • A 409 waybill_bulk_delete_conflict means eligibility changed during deletion; refresh before retrying.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
waybill_idsarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/bulk-delete-empty-drafts" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "waybill_ids": [6    "{{waybill_id}}",7    "{{waybill_id_2}}"8  ]9}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Empty drafted Waybills processed successfully",4  "data": {5    "scope": "selected",6    "deleted_count": 2,7    "deleted_ids": [8      "{{waybill_id}}",9      "{{waybill_id_2}}"10    ],11    "skipped": [12      {13        "id": "{{waybill_id_3}}",14        "reason": "has_deliveries"15      }16    ],17    "not_found": []18  }19}

Cancel Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/cancel

Cancellation delegates to the canonical atomic Waybill lifecycle owner. The assigned driver is captured inside that operation and receives one Waybill-level waybill_cancelled critical modal requiring acknowledgement. Push failure is recorded per channel without hiding the available in-app notification. The success payload is a compact authoritative Waybill with status: cancelled, item_count, summary, endpoint context and lifecycle operation_result; it intentionally omits embedded delivery and timeline detail.

Cancel a drafted, sealed, or assigned waybill before active transport starts.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/cancel" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill cancelled successfully",4  "data": {5    "id": "{{waybill_id}}",6    "waybill_number": "WB00001",7    "status": "cancelled",8    "driver_id": null,9    "vehicle_id": null,10    "cancelled_at": "2026-04-07T12:25:00Z",11    "item_count": 1,12    "summary": {13      "total_deliveries": 1,14      "total_unit_size": 1,15      "status_breakdown": {16        "pending": 117      }18    },19    "operation_result": {20      "action": "cancel",21      "status": "cancelled"22    }23  }24}

Change Waybill Status​

Delivery & Last-Mile / Waybills
PATCH/delivery/waybills/{{waybill_id}}/status

Changes a Waybill directly to an allowed non-receipt target status. Operational shortcuts write one final Waybill transition and atomically calculate the final child-delivery, child-assignment, driver-capacity, custody, and audit state; intermediate Waybill statuses are not persisted.

Method and authorization

PATCH /delivery/waybills/{waybill_id}/status. Dispatcher/admin or a user with Waybill management permission can perform direct target transitions. An authenticated assigned driver can advance their own already-assigned internal Waybill. A driver cannot self-assign while crossing the assignment boundary. Origin warehouse staff may seal their drafted warehouse-origin Waybill. Destination warehouse staff may close their received warehouse-destination Waybill.

Direct internal targets

drafted may target sealed, assigned, picked_up, in_transit, awaiting_receipt for a warehouse destination, or cancelled; sealed may target assigned, picked_up, in_transit, awaiting_receipt for a warehouse destination, or cancelled; assigned may target sealed, picked_up, in_transit, awaiting_receipt for a warehouse destination, or cancelled; picked_up may target in_transit or warehouse awaiting_receipt; in_transit may target warehouse awaiting_receipt; received may target closed. received is never an accepted target of this endpoint.

External/prime targets

drafted or sealed may target in_transit. External/prime carriers do not use assigned, picked_up, or awaiting_receipt.

Conditional request context

status and a non-blank reason are always required. reason is limited to 500 characters. Optional notes is limited to 2000 characters. Both values are returned on the matching Waybill status timeline event; retries cannot replace existing audit details. When an internal transition starts before assigned (drafted or sealed) and targets assigned, picked_up, in_transit, or awaiting_receipt, driver_id is required. Every new transition to awaiting_receipt requires at least one of signature_url or a non-empty photo_urls; a same-status retry does not require proof again. The backend derives the target delivery and assignment statuses; the client never sends child statuses or an internal action name.

Direct target effects

A jump from drafted crosses the seal boundary and creates the warehouse picking order when the origin is a warehouse, without persisting sealed; stock remains in the origin warehouse at this reservation step. Crossing assignment validates the active driver, primary vehicle, vehicle capacity, and zones, reserves capacity, and creates or updates child assignments directly to their final target state. Crossing pickup directly updates active children and assignment timestamps, performs idempotent sender-cash pickup accounting for a location origin, and for a warehouse origin atomically completes picking/handoffs, removes origin stock, records transfer_out, and moves custody to transit. awaiting_receipt leaves moving children in_transit, completes child assignments, records handover proof, and releases capacity once. Same-target retries are successful only with matching reason/notes and retry idempotent finance/paperwork without adding another Waybill timeline event.

Dedicated receipt flows

Do not send status: received here. Warehouse staff and external-carrier receipt use POST /delivery/waybills/{id}/receive, including the manifest verification and Idempotency-Key contract documented there. An assigned internal driver completing a customer/location Waybill uses POST /delivery/waybills/{id}/driver-action with action: received. These dedicated flows retain their existing delivery, payment, proof, receipt, discrepancy, custody, and earning side effects.

Response contract

The enriched Waybill detail returns top-level allowed_next_statuses and status_transition_options, and neither collection advertises received. Each transition option contains status, required_fields, required_headers, required_one_of, and conditional_fields; every option includes reason in required_fields. data.operation_result preserves action-specific results and adds previous_status, requested_status, authoritative status, reason, notes, allowed_next_statuses, and status_transition_options.

Important errors

HTTP 400 includes missing/invalid reason, invalid input, waybill_target_driver_required, and waybill_handover_proof_required; HTTP 403 means the actor cannot perform the target; HTTP 404 includes waybill_not_found and driver_not_found; HTTP 409 invalid_waybill_transition is returned for received and other unavailable targets, while other conflicts include waybill_status_context_conflict, waybill_driver_mismatch, and capacity/zone/driver conflicts. Unexpected service failures are logged and return HTTP 503 with localized code waybill_status_change_failed plus details.operation_id; the endpoint does not expose raw database errors.

Frontend integration contract

Render target choices from data.status_transition_options; received will not appear. Before submitting a target, collect every required_fields value, every required_headers value, one value from each required_one_of group, and fields activated by conditional_fields. Send the desired non-receipt status and required reason, not an action. Send optional notes when staff provide more context. Use the dedicated receipt endpoint or driver action for received. After success replace local Waybill detail with data, treat data.status as authoritative, and display reason plus optional notes from data.status_updates. Branch on stable code and display the localized message.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
statusstring—
reasonstring—
driver_idstring—
signature_urlstring—
photo_urlsarray<string>—
notesstring—
Staging
Request
curl
1curl --globoff --request PATCH "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/status" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "status": "awaiting_receipt",6  "reason": "Destination warehouse handover completed",7  "driver_id": "{{driver_id}}",8  "signature_url": "https://example.com/signatures/handover.png",9  "photo_urls": [10    "https://example.com/photos/handover.jpg"11  ],12  "notes": "Driver handed the Waybill to the destination warehouse"13}'

Replace {{placeholders}} with your own values before sending.

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill status updated successfully",4  "data": {5    "id": "{{waybill_id}}",6    "waybill_number": "WB00001",7    "status": "awaiting_receipt",8    "carrier_type": "internal",9    "destination_type": "warehouse",10    "driver_id": "{{driver_id}}",11    "status_updates": [12      {13        "old_status": "sealed",14        "new_status": "awaiting_receipt",15        "reason": "Destination warehouse handover completed",16        "notes": "Driver handed the Waybill to the destination warehouse",17        "actor_id": "{{actor_user_id}}",18        "timestamp": "2026-07-30T09:15:00.000Z"19      }20    ],21    "allowed_next_statuses": [],22    "status_transition_options": [],23    "operation_result": {24      "previous_status": "sealed",25      "requested_status": "awaiting_receipt",26      "status": "awaiting_receipt",27      "reason": "Destination warehouse handover completed",28      "notes": "Driver handed the Waybill to the destination warehouse",29      "driver_id": "{{driver_id}}",30      "delivery_status": "in_transit",31      "assignment_status": "completed",32      "reserved_capacity": 2,33      "released_capacity": 2,34      "crossed_seal": false,35      "crossed_assignment": true,36      "crossed_pickup": true,37      "crossed_in_transit": true,38      "crossed_handover": true,39      "direct_transition": true,40      "idempotent_replay": false,41      "allowed_next_statuses": [],42      "status_transition_options": []43    }44  }45}

Driver Action on Waybill​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/{{waybill_id}}/driver-action

Driver-only endpoint for internal waybill execution. Allowed actions: picked_up, in_transit, warehouse_handover, received. Active child deliveries include pending, assigned, picked_up, and in_transit, and cascade with the waybill: picked_up -> picked_up, in_transit -> in_transit, and received -> delivered only for customer/location destinations. For a warehouse origin, picked_up is the physical carrier handoff boundary: the picking order and outbound handoffs complete, warehouse stock becomes zero, a transfer_out movement is recorded per parcel, and custody becomes in transit. Transit stock belongs to neither warehouse. For warehouse destinations, warehouse_handover moves the Waybill to awaiting_receipt, completes the driver's child assignments, and leaves child deliveries in_transit until warehouse staff complete authoritative receipt. The awaiting_receipt Waybill remains the transport custody owner during that boundary. Returned and cancelled children are not overwritten. On received, waybill.received_by stores the authenticated driver's user ID (users.id), not the driver profile ID (drivers.id); Waybill Detail resolves it as received_by_user. Cash side effects follow child delivery payer rules: location-origin picked_up creates a fee-only pickup collection when the sender pays cash; final location received creates a COD-only dropoff collection or one combined COD+fee dropoff collection when the receiver pays both, records the COD payment as successful, and credits the delivery customer/merchant wallet immediately. Warehouse-origin pickup does not create sender cash. Final received credits one idempotent driver earning wallet transaction per delivered child using tenant_settings.driver_compensation. For final received, proof fields are optional. A known moving-delivery custody conflict returns HTTP 409 with code moving_delivery_requires_assigned_driver and a localized message. Other unexpected atomic database failures return HTTP 409 with stable code waybill_driver_action_failed and a localized display message; the transaction is rolled back and the action can be retried.

Path parameters

ParameterTypeDescription
waybill_idRequiredstring

Variable used inside the request path.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
actionstring—
current_latitudenumber—
current_longitudenumber—
notesstring—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${WAYBILL_ID}/driver-action" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "action": "picked_up",6  "current_latitude": 36.1901,7  "current_longitude": 44.0091,8  "notes": "Picked up from warehouse"9}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill action completed successfully",4  "data": {5    "waybill": {6      "id": "d0bb9c6b-25db-4e28-a5c1-1b709e3d11f1",7      "tenant_id": "{{tenant_id}}",8      "waybill_number": "WB00001",9      "origin_type": "warehouse",10      "origin_warehouse_id": "{{warehouse_id}}",11      "origin_location": null,12      "destination_type": "location",13      "destination_warehouse_id": null,14      "destination_location": {15        "latitude": 36.1901,16        "longitude": 44.0091,17        "full_address": "Erbil, Kurdistan Region"18      },19      "carrier_type": "internal",20      "driver_id": "{{driver_id}}",21      "vehicle_id": "{{vehicle_id}}",22      "external_carrier_ref": null,23      "assigned_by": "{{user_id}}",24      "assignment_time": "2026-04-07T12:20:00Z",25      "status": "picked_up",26      "sealed_at": "2026-04-07T12:15:00Z",27      "dispatched_at": "2026-04-07T12:35:00Z",28      "received_at": null,29      "received_by": null,30      "closed_at": null,31      "cancelled_at": null,32      "proof_of_delivery": null,33      "notes": "Batch for afternoon dispatch",34      "created_by": "{{user_id}}",35      "created_at": "2026-04-07T12:00:00Z",36      "updated_at": "2026-04-07T12:20:00Z"37    },38    "deliveries": [39      {40        "id": "a1b2c3d4-0001-0000-0000-000000000001",41        "delivery_code": 1001,42        "external_reference_id": "EXT-001",43        "status": "picked_up",44        "unit_size": 2,45        "pickup_location": {46          "id": "{{address_id}}",47          "full_address": "Warehouse A, Erbil",48          "latitude": 36.19,49          "longitude": 44.0150        },51        "delivery_location": {52          "id": "{{address_id}}",53          "full_address": "Customer Street 12, Erbil",54          "latitude": 36.2,55          "longitude": 44.0256        },57        "customer": {58          "id": "{{customer_id}}",59          "full_name": "Ahmed Ali",60          "phone": "+9647501234567"61        }62      }63    ]64  }65}

Get Driver Waybills​

Delivery & Last-Mile / Waybills
GET/delivery/driver-waybills

Driver-only list. Each waybill appears as one grouped job in the driver app. Without status, closed and cancelled waybills are excluded. Provide status=closed or status=cancelled to retrieve those terminal waybills explicitly. The response includes a complete statuses matrix for all waybill statuses, including statuses with a zero count. Matrix counts are for all waybills assigned to the authenticated driver and are not affected by the active status filter.

Query parameters

ParameterTypeDescription
pagestring

Page number

Example: 1

limitstring

Items per page

Example: 20

statusstring

Filter by waybill status

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/driver-waybills?page=1&limit=20&status=" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybills retrieved successfully",4  "data": [5    {6      "id": "{{waybill_id}}",7      "waybill_number": "WB00001",8      "origin_type": "warehouse",9      "origin_warehouse_id": "{{warehouse_id}}",10      "origin_location": null,11      "destination_type": "location",12      "destination_warehouse_id": null,13      "destination_location": {14        "latitude": 36.1901,15        "longitude": 44.0091,16        "full_address": "Erbil, Kurdistan Region"17      },18      "status": "sealed",19      "carrier_type": "internal",20      "assignment_time": "2026-04-07T12:20:00Z",21      "notes": "Batch for afternoon dispatch",22      "created_at": "2026-04-07T12:00:00Z",23      "updated_at": "2026-04-07T12:20:00Z",24      "item_count": 125    }26  ],27  "statuses": [28    {29      "status": "drafted",30      "label": "Drafted",31      "count": 032    },33    {34      "status": "sealed",35      "label": "Sealed",36      "count": 137    },38    {39      "status": "assigned",40      "label": "Assigned",41      "count": 042    },43    {44      "status": "picked_up",45      "label": "Picked Up",46      "count": 047    },48    {49      "status": "in_transit",50      "label": "In Transit",51      "count": 052    },53    {54      "status": "awaiting_receipt",55      "label": "Awaiting Receipt",56      "count": 057    },58    {59      "status": "received",60      "label": "Received",61      "count": 062    },63    {64      "status": "closed",65      "label": "Closed",66      "count": 067    },68    {69      "status": "cancelled",70      "label": "Cancelled",71      "count": 072    }73  ],74  "total": 1,75  "page": 1,76  "limit": 2077}

Get Driver Waybill Status Summary​

Delivery & Last-Mile / Waybills
GET/delivery/driver-waybills/status-summary

Driver-only status counts for waybill tabs/sections in the app. Returns the complete ordered status matrix with labels, including statuses with a zero count.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/driver-waybills/status-summary" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill status summary retrieved successfully",4  "statuses": [5    {6      "status": "drafted",7      "label": "Drafted",8      "count": 09    },10    {11      "status": "sealed",12      "label": "Sealed",13      "count": 214    },15    {16      "status": "assigned",17      "label": "Assigned",18      "count": 019    },20    {21      "status": "picked_up",22      "label": "Picked Up",23      "count": 124    },25    {26      "status": "in_transit",27      "label": "In Transit",28      "count": 329    },30    {31      "status": "awaiting_receipt",32      "label": "Awaiting Receipt",33      "count": 034    },35    {36      "status": "received",37      "label": "Received",38      "count": 439    },40    {41      "status": "closed",42      "label": "Closed",43      "count": 044    },45    {46      "status": "cancelled",47      "label": "Cancelled",48      "count": 049    }50  ]51}

Return Waybill Children (Partial Return)​

Delivery & Last-Mile / Waybills
POST/delivery/waybills/:id/return-children

When initiated by another actor, the affected driver receives one Waybill-level waybill_returned important persistent banner, not one notification per returned child. A driver performing their own return does not receive a redundant notification. Replaying the same successful Idempotency-Key does not emit another notification.

Idempotently return one or more child deliveries. Idempotency-Key is required. Before warehouse receipt, the existing partial-return behavior remains unchanged. After a warehouse-destination Waybill is received, each physically present or damaged child creates a separate internal reverse delivery from the Waybill destination back to its origin. The original forward delivery keeps its primary status and current Waybill membership; only return_status changes to return_approved. The reverse delivery is linked through return_requests, starts as pending, and always has delivery_fee=0, cod_amount=0, payment_method=wallet, and payment_status=paid, so no customer wallet charge or refund is created. Inventory remains at the destination while the reverse order is pending or moving and is moved back only when that return delivery reaches delivered or staged_at_warehouse. A child with an open missing_on_receive discrepancy cannot be returned through this endpoint and returns HTTP 409 delivery_has_open_exception. Destination staff must first use the dedicated discrepancy resolver with found or lost; lost resolves the discrepancy, marks the actual delivery returned without creating a physical reverse order, and settles that delivery through its existing service return-refund policy. The response is enriched Waybill detail plus operation_result.return_orders, with original IDs, return-request IDs, return-delivery IDs/codes/statuses, and exception IDs. Frontend must navigate to return_delivery_id when staff need to manage the reverse order.

Path parameters

ParameterTypeDescription
idRequiredstring

Waybill UUID

Example: <waybill_id>

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Idempotency-Keystring

Example: waybill-return-{{$guid}}

Body parameters

ParameterTypeDescription
delivery_idsarray<string>—
reasonstring—
notesstring—
photo_urlsarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/waybills/${ID}/return-children" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --header "Idempotency-Key: waybill-return-${_GUID}" \5  --data-raw '{6  "delivery_ids": [7    "<delivery_id_1>",8    "<delivery_id_2>"9  ],10  "reason": "refused",11  "notes": "Customer refused on doorstep",12  "photo_urls": [13    "https://example.com/photo1.jpg"14  ]15}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill children returned successfully",4  "data": {5    "id": "wb-uuid",6    "waybill_number": "WB000123",7    "status": "received",8    "operation_result": {9      "waybill_id": "wb-uuid",10      "status": "received",11      "return_order_count": 1,12      "return_requested_delivery_ids": [13        "d1"14      ],15      "return_orders": [16        {17          "original_delivery_id": "d1",18          "original_delivery_code": "1001",19          "return_request_id": "return-request-uuid",20          "return_delivery_id": "return-delivery-uuid",21          "return_delivery_code": "1002",22          "return_delivery_status": "pending",23          "exception_id": "exception-uuid"24        }25      ],26      "missing_resolved_delivery_ids": [],27      "missing_resolved_count": 0,28      "financial_reversal": false,29      "idempotent_replay": false30    }31  }32}

List Waybill Returns​

Delivery & Last-Mile / Waybills
GET/delivery/waybills/:id/returns

Paginated Waybill return history. Each row contains the return exception, evidence and enriched actors, the original forward delivery, the linked return request, and the separate reverse delivery with its latest assignment, driver, and vehicle. Historical rows created before return-order linkage remain visible with null return_request and return_delivery.

Path parameters

ParameterTypeDescription
idRequiredstring

Waybill UUID

Example: <waybill_id>

Query parameters

ParameterTypeDescription
pagestring

Example: 1

limitstring

Example: 20

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Staging
Request
curl
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/waybills/${ID}/returns?page=1&limit=20" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Waybill returns retrieved successfully",4  "data": [5    {6      "id": "exception-uuid",7      "delivery_id": "original-delivery-uuid",8      "exception_type": "refused",9      "reason": "refused",10      "notes": "Return to origin",11      "status": "resolved",12      "resolution_type": "returned",13      "trigger_point": "rejected_at_destination",14      "photo_urls": [15        "https://example.com/return.jpg"16      ],17      "proof": null,18      "reported_at": "2026-07-31T10:00:00.000Z",19      "resolved_at": "2026-07-31T10:00:00.000Z",20      "reported_by_user": {21        "id": "user-uuid",22        "full_name": "Dispatcher User",23        "email": "dispatcher@example.com",24        "phone": "+9647500000000",25        "avatar_url": null26      },27      "resolved_by_user": {28        "id": "user-uuid",29        "full_name": "Dispatcher User",30        "email": "dispatcher@example.com",31        "phone": "+9647500000000",32        "avatar_url": null33      },34      "original_delivery": {35        "id": "original-delivery-uuid",36        "delivery_code": "DEL1001",37        "barcode": "DEL1001",38        "status": "delivered",39        "return_status": "return_approved",40        "current_warehouse_id": "destination-warehouse-uuid"41      },42      "delivery": {43        "id": "original-delivery-uuid",44        "delivery_code": "DEL1001",45        "status": "delivered",46        "return_status": "return_approved"47      },48      "return_request": {49        "id": "return-request-uuid",50        "status": "approved",51        "return_reason": "refused",52        "notes": "Return to origin",53        "requested_at": "2026-07-31T10:00:00.000Z",54        "processed_at": "2026-07-31T10:00:00.000Z",55        "received_at": null,56        "warehouse": {57          "id": "origin-warehouse-uuid",58          "name": "Origin Warehouse",59          "code": "ORG"60        },61        "processed_by_user": {62          "id": "user-uuid",63          "full_name": "Dispatcher User",64          "email": "dispatcher@example.com",65          "phone": "+9647500000000",66          "avatar_url": null67        }68      },69      "return_delivery": {70        "id": "return-delivery-uuid",71        "delivery_code": "DEL1002",72        "barcode": "DEL1002",73        "status": "pending",74        "pickup_warehouse_id": "destination-warehouse-uuid",75        "dropoff_warehouse_id": "origin-warehouse-uuid",76        "delivery_fee": 0,77        "cod_amount": 0,78        "payment_method": "wallet",79        "payment_status": "paid",80        "assignment": null81      }82    }83  ],84  "total": 1,85  "page": 1,86  "limit": 2087}