Trips
20 API calls in this section.
Trip management — driver work plans with ordered delivery stops.
A Trip groups multiple deliveries for one driver. Storage is delivery-level (each delivery holds trip_id + trip_sequence), while an optional trip-level stop order stores dispatcher-defined stop priorities. The GET detail endpoint returns the effective stop_list for the dispatcher and driver execution views. Pickup stops are grouped only when their coordinates match, and drop-off stops are grouped only when their coordinates match. Address IDs, labels, names, and formatted addresses are not used for stop matching. A grouped drop-off is always placed after every pickup containing one of its parcels. Each stop exposes a stable stop_key, and each delivery exposes its active assignment_status, next_action, and bulk_eligible state for driver group actions. Successful pickup rows from the stop bulk-action endpoint are published as one bounded notification operation; customer and driver events remain per applicable delivery while dispatchers/admins receive one operation summary. Notification delivery is asynchronous and is never the source of trip or pickup truth. Every trip domain error returns a stable machine-readable code, a localized display message, and data (usually null; ordered delivery validation uses it for per-order results).
List Trips
/delivery/tripsList trips with optional filters, column-level filters, sorting, and pagination. Returns item_count per trip.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number Example: |
limit | string | Items per page (max 100) Example: |
status | string | Filter by status: planned, Example: |
driver_id | string | Filter by driver UUID |
trip_type | string | Filter by type: |
trip_number | string | Search by trip number (partial) |
from_date | string | Start date YYYY-MM-DD (normalized to 00:00:00) |
to_date | string | End date YYYY-MM-DD (normalized to 23:59:59) |
sort_by | string | Sort by: Example: |
sort_order | string | Sort order: asc or desc Example: |
column_filters | string | JSON object. Supported keys: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/trips?page=1&limit=20&status=planned&driver_id=&trip_type=&trip_number=&from_date=&to_date=&sort_by=created_at&sort_order=desc&column_filters=" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Trips retrieved successfully",4 "data": {5 "trips": [6 {7 "id": "trip-uuid",8 "trip_number": "TRIP-00001",9 "status": "planned",10 "trip_type": "multi_pickup",11 "notes": "Morning run",12 "started_at": null,13 "completed_at": null,14 "created_at": "2026-04-11T10:00:00Z",15 "updated_at": "2026-04-11T10:00:00Z",16 "item_count": 3,17 "driver": {18 "id": "driver-uuid",19 "full_name": "Ahmed Hassan",20 "phone": "+9647501234567"21 },22 "vehicle": {23 "id": "vehicle-uuid",24 "name": "Toyota Hiace",25 "code": "VH-001"26 }27 }28 ],29 "total": 1,30 "page": 1,31 "limit": 2032 }33}Create Trip
/delivery/tripsRealtime mobile contract
a newly created Trip assignment is immediately assigned. Its existing delivery_realtime_assignments notification therefore emits assignment_status: "assigned" and requires_driver_response: false. Mobile displays it as information and does not show Accept/Reject. Missing or unknown actionability is also non-actionable.
Create a new trip. Metadata fields are optional when creating an empty trip. driver_id is required when deliveries is non-empty.
trip_type:multi_pickup|multi_dropoff| mixed (optional, informational)deliveries: optional array of {delivery_id, sequence?}. Sequence auto-assigned if not provided.All submitted deliveries are validated before the trip row or assignment rows are created. Validation results follow request order.
Unassigned pending deliveries are assigned immediately to the trip driver using the normal driver, primary-vehicle, capacity, zone, team, service-policy, audit, realtime, and notification rules. Shared driver and policy context is loaded once, and all missing assignments are written through the atomic batch-assignment transaction.
Newly assigned rows use
status=assigned; route calculation is scheduled asynchronously after commit. One bulk realtime summary and one bulk driver notification represent the newly assigned rows, withassignment_status=assignedandrequires_driver_response=false.A delivery already assigned to the same driver is reused. A delivery assigned to another driver is rejected and is never automatically reassigned.
Eligible delivery statuses: pending, assigned. Only internal operational deliveries are accepted.
Deliveries linked to any Waybill, route, route session, or another trip are rejected.
P&D parent orders and inactive draft legs are rejected; attach only the currently active internal child leg.
Frontend integration contract
Select a trip driver before sending a non-empty
deliveriesarray.After success, use the returned deliveries; newly assigned deliveries have
status=assigned.On HTTP 409 with
code=trip_delivery_validation_failed, keep the form and show eachdata.results[].error_messageagainst its matchingdelivery_id.
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
driver_id | string | — |
vehicle_id | string | — |
trip_type | string | — |
notes | string | — |
deliveries | array<object> | — |
deliveries.delivery_id | string | — |
deliveries.sequence | number | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "driver_id": "driver-uuid",6 "vehicle_id": "vehicle-uuid",7 "trip_type": "multi_pickup",8 "notes": "Morning delivery run",9 "deliveries": [10 {11 "delivery_id": "delivery-uuid-1",12 "sequence": 113 },14 {15 "delivery_id": "delivery-uuid-2",16 "sequence": 217 }18 ]19}'Response examples
1{2 "success": true,3 "message": "Trip created successfully",4 "data": {5 "id": "trip-uuid",6 "trip_number": "TRIP-00001",7 "status": "planned",8 "trip_type": "multi_pickup",9 "notes": "Morning delivery run",10 "driver": {11 "id": "driver-uuid",12 "full_name": "Ahmed Hassan",13 "phone": "+9647501234567"14 },15 "vehicle": {16 "id": "vehicle-uuid",17 "name": "Toyota Hiace",18 "code": "VH-001"19 },20 "deliveries": [21 {22 "id": "delivery-uuid-1",23 "delivery_code": 1001,24 "barcode": "BC001",25 "status": "assigned",26 "trip_sequence": 127 },28 {29 "id": "delivery-uuid-2",30 "delivery_code": 1002,31 "barcode": "BC002",32 "status": "assigned",33 "trip_sequence": 234 }35 ],36 "summary": {37 "total": 2,38 "planned": 2,39 "in_progress": 0,40 "delivered": 0,41 "returned": 0,42 "cancelled": 0,43 "with_return_request": 044 },45 "driver_execution": {46 "stop_list": [47 {48 "stop_type": "pickup",49 "location": {50 "latitude": 33.3,51 "longitude": 44.452 },53 "deliveries": [54 {55 "delivery_id": "delivery-uuid-1",56 "delivery_code": 1001,57 "barcode": "BC001",58 "status": "assigned",59 "trip_sequence": 160 }61 ],62 "is_complete": false63 }64 ],65 "current_stop": {66 "stop_type": "pickup",67 "location": {},68 "deliveries": []69 },70 "next_stop": null71 }72 }73}Get Trip Detail
/delivery/trips/{{trip_id}}Get full trip detail including
Trip header (number, status, type, general notes) and concurrency token
updated_atstatus_updates: status-only timeline with previous/new status, timestamp, reason, and notestrip_events: complete append-only staff history for users with trip-management permissionavailable_actions: manager actions currently allowed (cancel,switch_driver)Driver and vehicle info
Ordered deliveries and
deliveries_mode(liveorhistorical)Summary counts (total, per-status)
driver_execution: coordinate-groupedstop_list,current_stop,next_stop
Live deliveries and stop delivery references include assignment_status plus the literal next_action: start_delivery, pickup, in_transit, complete, or null. bulk_eligible is true only when one of those actions is available. Each stop includes the stable stop_key that must be returned unchanged to stop-order or bulk-action requests. Assignment enrichment is one batched query for the whole trip, never one query per delivery.
trip_events[] records trip_created, trip_updated, driver_assigned, driver_unassigned, driver_changed, deliveries_added, deliveries_removed, deliveries_reordered, stops_reordered, trip_started, trip_completed, trip_cancelled, deliveries_moved_out, and deliveries_moved_in. Each event returns event_type, timestamp, actor fields (actor_id, actor_name, actor_role), status context, reason/notes, and optional changes. New move events also return source_trip_id, source_trip_number, target_trip_id, and target_trip_number; older move events may contain only the IDs. Historical events created before this feature may have null actor fields because the previous data did not preserve the actor; the API never invents one. Driver-only callers do not receive trip_events, available_actions, or historical delivery snapshots.
For a cancelled trip, the manager response returns the cancellation-time snapshot through data.deliveries, sets data.deliveries_mode=historical, calculates summary from that snapshot, returns no available actions, and keeps driver_execution.stop_list, current_stop, and next_stop empty. Each historical delivery contains id, delivery_code, barcode, pickup_location, dropoff_location, unit_size, cancellation-time status, payment_method, cod_amount, external_reference_id, and trip_sequence. Cancelled trips created before snapshot storage may return an empty historical list because former membership is not invented without persisted evidence.
Stop ordering rules
Pickup locations are grouped only by exact valid latitude/longitude coordinates.
Drop-off locations are grouped only by exact valid latitude/longitude coordinates.
Pickup and drop-off remain separate stop types even when their coordinates match.
Locations without valid coordinates are not grouped.
Address IDs, labels, names, and formatted addresses do not determine grouping.
A drop-off is placed only after every pickup stop required by the deliveries inside that drop-off.
A saved dispatcher stop order is preferred; obsolete keys are ignored and new keys are inserted in a valid deterministic position.
A pickup or drop-off shared by every current order remains first or last respectively.
Without a saved stop order, delivery
trip_sequenceremains the preference whenever it does not violate pickup-before-drop-off.current_stopis the first incomplete stop in the effective order;next_stopis the following stop.
Frontend integration contract
Build the Trip Events section from
data.trip_eventsin array order, usingevent_typeas the machine-readable display mapping key.Show actor name/role and timestamp for each event; show reason, notes, and changes only when present. For move events, display
source_trip_numberandtarget_trip_numberwhen present, falling back to their IDs for older history.Show Cancel Trip and Switch Driver only when their literal values are present in
data.available_actions.Render
data.deliveriesin the existing Deliveries tab. Whendata.deliveries_mode=historical, keep the list read-only.Keep
data.status_updatesonly for the compact status timeline. Do not treat top-leveldata.notesas an event note.Render
data.driver_execution.stop_list,current_stop, andnext_stopdirectly. Usestop_keyand select only deliveries whosenext_actionequals the group button action.
G2G origin display
each live data.deliveries[] row includes nullable source_transfer_manifest_id and g2g_origin. A non-null g2g_origin contains { "branchName": string|null, "branchAddress": string|null } from a valid saved manifest_origin_v1 snapshot; staged children inherit usable origin from their same-tenant, non-deleted parent when needed only if their manifest ID is absent or matches the parent. Normal orders and orders without usable saved origin return null. When source_transfer_manifest_id is non-null, render a separate read-only Original G2G location line on the delivery card, using any available branch name or address; if g2g_origin is null, show Origin unavailable. Hide the line only when the manifest ID is null or absent. Keep pickup_location, dropoff_location, driver_execution, stop grouping, maps, and action targets unchanged. The same batched delivery query returns the metadata; do not fetch per-card details or current branch addresses. Historical cancelled-trip snapshots do not acquire these new fields and their origin line remains hidden.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Trip retrieved successfully",4 "data": {5 "id": "trip-uuid",6 "trip_number": "TRIP-00001",7 "status": "in_progress",8 "updated_at": "2026-09-10T10:00:00.000Z",9 "deliveries": [10 {11 "id": "d1",12 "delivery_code": 1001,13 "status": "picked_up",14 "trip_sequence": 1,15 "assignment_status": "picked_up",16 "next_action": "in_transit",17 "source_transfer_manifest_id": "manifest-uuid",18 "g2g_origin": {19 "branchName": "Original branch",20 "branchAddress": "Original branch address"21 }22 },23 {24 "id": "d2",25 "delivery_code": 1002,26 "status": "en_route_to_pickup",27 "trip_sequence": 2,28 "assignment_status": "en_route_to_pickup",29 "next_action": "pickup",30 "source_transfer_manifest_id": null,31 "g2g_origin": null32 }33 ],34 "driver_execution": {35 "stop_list": [36 {37 "stop_key": "pickup:33.3,44.4",38 "stop_type": "pickup",39 "location": {40 "latitude": 33.3,41 "longitude": 44.442 },43 "deliveries": [44 {45 "delivery_id": "d1",46 "delivery_code": 1001,47 "status": "picked_up",48 "trip_sequence": 1,49 "assignment_status": "picked_up",50 "next_action": null,51 "bulk_eligible": false52 },53 {54 "delivery_id": "d2",55 "delivery_code": 1002,56 "status": "en_route_to_pickup",57 "trip_sequence": 2,58 "assignment_status": "en_route_to_pickup",59 "next_action": "pickup",60 "bulk_eligible": true61 }62 ],63 "is_complete": false64 }65 ],66 "current_stop": {67 "stop_key": "pickup:33.3,44.4",68 "stop_type": "pickup",69 "is_complete": false70 },71 "next_stop": null72 }73 }74}Update Trip
/delivery/trips/{{trip_id}}Update planned-trip metadata only. Supported fields are optional trip_type and notes. Driver and vehicle changes are intentionally rejected with HTTP 409 code=trip_driver_switch_endpoint_required; use POST /delivery/trips/{id}/switch-driver so assignment, custody, capacity, audit, and both-driver notifications stay consistent.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
trip_type | string | — |
notes | string | — |
1curl --globoff --request PATCH "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "trip_type": "multi_dropoff",6 "notes": "Updated notes"7}'Response examples
1{2 "success": true,3 "message": "Trip updated successfully",4 "data": {5 "id": "trip-uuid",6 "status": "planned"7 }8}Switch Trip Driver
/delivery/trips/{{trip_id}}/switch-driverChanges the driver of a planned or in_progress trip. Requires trip-management permission. driver_id and reason are required; notes is optional. The backend selects the new driver's primary active vehicle.
Business rules
The new driver must be active and available, have an active primary vehicle with enough remaining capacity, and satisfy every active trip delivery's zone, team, and service-vehicle rules.
Completed, failed, cancelled, and returned deliveries are not reassigned. Every non-terminal member must have exactly one active assignment to the current trip driver.
The switch is atomic and preserves trip membership, trip sequence, stop order, delivery progress, pickup/delivery timestamps, and trip status. Active assignment ownership, custody, vehicle, and capacity move immediately to the new driver. Pre-pickup assignment acceptance follows tenant driver settings.
Repeating the request with the current driver returns
changed=falseand creates no duplicate event or notification.One in-app and push notification is created for each affected driver. The previous driver receives a critical modal requiring acknowledgement. The new driver receives a normal toast before pickup or an important persistent banner after pickup. Post-pickup notifications include the counterparty contact actions. Notification acknowledgement does not delay the custody switch.
Success data contains id, trip_number, authoritative status, changed, previous_driver_id, driver_id, vehicle_id, delivery_count, and picked_up_delivery_count.
Important errors
HTTP 400
code=validation_errorfor missing/invalid fields.HTTP 403
code=insufficient_permissionsorassignment_change_disabled.HTTP 404
code=trip_not_foundordriver_not_found.HTTP 409
code=trip_driver_switch_not_allowed,trip_driver_required,new_driver_not_active,new_driver_has_no_vehicle,delivery_assigned_to_different_driver,invalid_delivery_status_for_driver_switch, zone/team/service-vehicle errors, orinsufficient_vehicle_capacity_for_new_driver.
Frontend integration contract
Show Switch Driver only when the trip has a driver and status is
plannedorin_progress.Send only
driver_id,reason, and optionalnotes; do not sendvehicle_id.On success, replace the displayed driver and vehicle using the returned IDs or reload trip detail.
Branch on
codeand display the localizedmessage.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
driver_id | string | — |
reason | string | — |
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/switch-driver" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "driver_id": "new-driver-uuid",6 "reason": "Driver shift changed",7 "notes": "Coordinate the package handover with dispatch"8}'Response examples
1{2 "success": true,3 "message": "Trip driver changed successfully",4 "data": {5 "id": "trip-uuid",6 "trip_number": "TRIP-00016",7 "status": "in_progress",8 "changed": true,9 "previous_driver_id": "old-driver-uuid",10 "driver_id": "new-driver-uuid",11 "vehicle_id": "vehicle-uuid",12 "delivery_count": 3,13 "picked_up_delivery_count": 214 }15}Change Trip Status
/delivery/trips/{{trip_id}}/statusStarts, completes, or cancels an Eaglepost trip. Requires manage:operations:tenant, manage:module:delivery, or system-admin access.
Start rules
in_progressrequires aplannedtrip with a driver and at least one delivery.Every member may be
assigned,en_route_to_pickup,picked_up, orin_transitand must have exactly one active assignment to the trip driver. This allows a planned trip to start after receiving already-active orders through the move endpoint without resetting their progress.Starting changes only the trip status and start timestamp; it does not reset order or assignment progress.
Repeating
in_progressis idempotent withchanged=false.
Completion rules
completedis accepted only for anin_progressor alreadycompletedtrip.Existing
delivered,failed,cancelled, andreturnedmembers are retained as successful unchanged results.Every remaining member is marked
deliveredthrough the same individual delivery-status flow used byPOST /delivery/deliveries/{deliveryId}/status. This preserves assignment completion, payment/COD handling, package outcomes, capacity, earnings, notifications, audit history, P&D child handling, and the individual flow's validation behavior. The trip driver is supplied if that flow must recreate a missing assignment.After delivery processing, the endpoint atomically rechecks current membership. When every current member is terminal, it explicitly completes the trip and records its completion timestamp/history without changing order state, assignment, driver, or order history. This also handles a source trip left with only terminal orders after a move.
Delivery processing uses bounded concurrency. Results are returned in trip sequence order.
A fully successful retry of an already completed trip is idempotent.
If any member fails, successful member changes remain persisted, the API returns HTTP 409
code=trip_deliveries_not_completed, anddata.statusremains authoritative.
Move interaction
Moving orders never starts or completes either trip. Only this later explicit status request may change the trip status.
Cancellation rules
cancelleddelegates to the same atomic cancellation service asPOST /delivery/trips/{id}/cancel; the dedicated endpoint remains backward compatible.Every current member must still be
pending,assigned, oren_route_to_pickup. A picked-up, in-transit, delivered, cancelled, returned, exception, or otherwise non-cancellable member blocks the whole trip cancellation.Cancellation releases active assignments and driver capacity, returns every member to
pending, and clearstrip_idandtrip_sequence.Repeating cancellation is idempotent and creates no duplicate notification. A real cancellation sends the assigned driver one critical modal requiring acknowledgement plus push notification.
Completion response data contains id, previous_status, authoritative status, changed, timestamps, available_actions, order-wise results, total_success, total_changed, and total_failed. Cancellation returns the existing cancellation result including release counts and authoritative status.
Important errors
HTTP 400
code=validation_errorfor a missing/invalid status or invalid trip UUID. Missing completion or cancellation reason returns HTTP 400error=reason_requiredandfield=reason.HTTP 403
code=insufficient_permissions.HTTP 404
code=trip_not_found.HTTP 409
code=invalid_trip_status_transition,trip_has_no_deliveries,trip_deliveries_not_ready_to_start,trip_deliveries_not_completed, ortrip_deliveries_not_releasable.
Frontend integration contract
Start sends
{ "status": "in_progress" }.Complete sends
{ "status": "completed", "reason": "...", "notes": "..." }.Cancel may now send
{ "status": "cancelled", "reason": "..." }to this PATCH endpoint. The dedicated POST cancel endpoint remains supported.Never send
plannedto this endpoint.On HTTP 200, use the authoritative
data.statusand timestamps. Completion also returnsdata.results.On completion HTTP 409, show each failed
data.results[]entry's localizedmessage. Branch on machine codes, never message text.No navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
statusRequired | string |
|
reason | string | (required for |
notesOptional | string | is optional. Successful status changes persist |
1curl --globoff --request PATCH "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/status" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "status": "cancelled",6 "reason": "Trip cancelled by dispatcher",7 "notes": "Vehicle issue reported before pickup"8}'Response examples
1{2 "success": true,3 "message": "Trip status updated successfully",4 "data": {5 "id": "trip-uuid",6 "previous_status": "planned",7 "status": "in_progress",8 "changed": true,9 "started_at": "2026-07-29T12:30:00Z",10 "completed_at": null,11 "released_delivery_count": 0,12 "released_assignment_count": 0,13 "available_actions": [14 "cancel"15 ]16 }17}List Eligible Deliveries for Trip
/delivery/trips/eligible-deliveriesReturns operational deliveries that can be added to a trip.
Eligibility rules
Status is
pendingorassignedCarrier is
internalNot deleted
Not linked to a Waybill, route, route session, or trip
Not a P&D parent order
For staged P&D, only the currently active child leg is returned: the pending/assigned inbound leg before staging, or the pending/assigned outbound leg after staging. Draft future legs are excluded.
Supports indexed search by delivery code, barcode, or external_reference_id and indexed newest-first pagination. The same rules are revalidated by POST; the picker response is not trusted as authorization.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number Example: |
limit | string | Items per page (max 100) Example: |
search | string | Search by barcode or |
tenant_id | string | Tenant ID |
language | string | Language Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/trips/eligible-deliveries?page=1&limit=20&search=&tenant_id=&language=en" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Eligible deliveries retrieved successfully",4 "data": [5 {6 "id": "delivery-uuid-1",7 "delivery_code": 1001,8 "barcode": "BC001",9 "status": "pending",10 "external_reference_id": null,11 "payment_method": "cod",12 "cod_amount": 15000,13 "unit_size": 1,14 "pickup_location": {15 "latitude": 33.3,16 "longitude": 44.417 },18 "delivery_location": {19 "latitude": 33.5,20 "longitude": 44.621 },22 "pickup_warehouse": {23 "id": "wh-uuid",24 "name": "Main WH",25 "code": "WH01"26 },27 "dropoff_warehouse": null,28 "pickup_zone": {29 "id": "zone-uuid",30 "name": "Zone A",31 "code": "ZA"32 },33 "dropoff_zone": null34 }35 ],36 "total": 5,37 "page": 1,38 "limit": 2039}Validate Scanned Trip Delivery
/delivery/trips/deliveries/scan-validateRead-only pre-create validation for trip scan mode. Use this when a hardware scanner or camera returns the printed shipment-label value such as DEL2993.
barcodeis required. The backend trims and uppercases it, then resolves by exact storedbarcodeor bydelivery_codeafter stripping the printedDELprefix.
Business rules
This endpoint performs no writes and does not reserve capacity.
Driver, delivery, staged-child, and active-assignment context is resolved in one database call per scan.
The requested driver must exist for the tenant and be active.
If the scanned label resolves to a staged/P&D parent order, the backend resolves that parent to the currently active child delivery leg and validates the child. The response
data.deliveryis always the delivery row the trip should add.Valid means the resolved delivery currently passes trip membership rules and any active assignment already belongs to the requested driver.
It rejects deleted deliveries, deliveries attached to a Waybill, route, route session, or another trip, non-internal carriers, non-pending/non-assigned statuses, active assignments for a different driver, and staged parents with no active child leg.
The final
POST /delivery/tripsrequest remains authoritative and repeats validation before creating the trip and assigning deliveries.
Frontend integration contract
Call this endpoint for each scan after a driver is selected.
On
data.valid=true, always add/check duplicates bydata.delivery.id. If the scanned parent was resolved,data.resolved_from_parentidentifies the scanned parent and child, but the frontend must submit the returned childdata.delivery.id.If
data.delivery.idis already in the local unsaved trip list, show the duplicate warning and do not add another row. Otherwise add it to the same local delivery list used by searched rows and displaydata.message.On
data.valid=false, displaydata.messageand branch ondata.codeonly if UI behavior needs a machine reason.Submit searched and scanned rows together to
POST /delivery/tripsasdeliveries: [{ delivery_id, sequence }]. No navigation action is returned.
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
driver_idRequired | string | is required. It is the intended trip driver UUID. |
barcode | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/deliveries/scan-validate" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "driver_id": "driver-uuid",6 "barcode": "DEL2993"7}'Response examples
1{2 "success": true,3 "message": "Scanned delivery validated",4 "data": {5 "scanned_code": "DEL4108",6 "normalized_delivery_code": "4108",7 "valid": true,8 "assignment_action": "assign",9 "message": "Delivery is ready to add to this trip",10 "resolved_from_parent": {11 "parent_delivery_id": "parent-delivery-uuid",12 "parent_delivery_code": "4108",13 "delivery_id": "child-delivery-uuid",14 "delivery_code": "4108B"15 },16 "delivery": {17 "id": "child-delivery-uuid",18 "delivery_code": "4108B",19 "barcode": "DEL4108B",20 "external_reference_id": "ORDER-4108",21 "status": "pending",22 "unit_size": 1,23 "payment_method": "cod",24 "cod_amount": 15000,25 "pickup_location": {26 "latitude": 33.3,27 "longitude": 44.428 },29 "dropoff_location": {30 "latitude": 33.5,31 "longitude": 44.632 }33 }34 }35}Validate Trip Deliveries
/delivery/trips/{{trip_id}}/deliveries/validateRead-only preflight for adding deliveries to a planned trip. The trip must have a driver. Results are returned in the exact submitted order and contain delivery_id, delivery_code, valid, and either assignment_action or localized error_key/error_message.
Assignment actions
assign: pending delivery has no active assignment and will be assigned to the trip driver during sync.keep: active assignment already belongs to the trip driver.An active assignment for another driver returns
delivery_assigned_to_different_driver; the backend never reassigns it automatically.
This endpoint performs no writes. The final create/sync endpoint repeats the same validation. Frontend should show each error beside the matching delivery and submit only when every selected delivery is valid.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
deliveries | array<object> | — |
deliveries.delivery_id | string | — |
deliveries.sequence | number | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/deliveries/validate" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "deliveries": [6 {7 "delivery_id": "delivery-uuid-1",8 "sequence": 19 },10 {11 "delivery_id": "delivery-uuid-2",12 "sequence": 213 }14 ]15}'Response examples
1{2 "success": true,3 "message": "Trip delivery validation completed",4 "data": {5 "results": [6 {7 "delivery_id": "delivery-uuid-1",8 "delivery_code": 1001,9 "valid": true,10 "assignment_action": "assign"11 },12 {13 "delivery_id": "delivery-uuid-2",14 "delivery_code": 1002,15 "valid": false,16 "error_key": "delivery_assigned_to_different_driver",17 "error_message": "Delivery is already assigned to a different driver"18 }19 ],20 "total_valid": 1,21 "total_failed": 122 }23}Sync Trip Deliveries
/delivery/trips/{{trip_id}}/deliveriesRealtime mobile contract
each newly attached unassigned delivery is immediately assigned to the Trip driver. The existing realtime assignment notification emits assignment_status: "assigned" and requires_driver_response: false; it is informational and never asks the driver to accept an already assigned Trip delivery. Use POST /delivery/trips/{trip_id}/remove-deliveries instead when removal must preserve order status and driver assignment.
Sync the full delivery membership of a trip. Only allowed while trip is planned.
This is a replace operation — the array sent becomes the exact set of deliveries on the trip. Delivery attachment, sequence changes, removals, assignment release, capacity release, and the batched deliveries_added, deliveries_removed, or deliveries_reordered trip events commit atomically. sequence is optional; if omitted, position index (1-based) is used. Send an empty array [] to remove all deliveries.
Assignment behavior
The trip must have a driver for a non-empty membership.
All incoming deliveries are validated before assignment or membership writes; failures are returned in request order.
Pending deliveries without active assignments are assigned through one shared preflight and the atomic batch-assignment transaction; existing validation results are reused instead of querying every assignment twice.
Newly assigned rows use
status=assigned; route calculation is scheduled asynchronously after commit. One bulk realtime summary and one bulk driver notification represent the newly assigned rows, withassignment_status=assignedandrequires_driver_response=false.An existing active assignment is accepted only when it belongs to the same driver. A different-driver assignment is rejected and never automatically reassigned.
Removed members are unassigned, capacity is released, status returns to
pending, and trip fields are cleared.
Eligibility rules for incoming deliveries
Internal delivery with status
pendingorassignedNot deleted and not linked to a Waybill, route, route session, or another trip
Not a P&D parent; only the currently active internal child leg may be attached
Frontend integration contract
Use this sync endpoint for add/remove membership; send the complete desired replacement array.
Call the validate endpoint to display ordered per-delivery results before sync.
Ensure the trip has
driver_idbefore submitting a non-empty array.Replace local trip data with
datafrom a successful response.On HTTP 409 with
code=trip_delivery_validation_failed, keep previous membership and show eachdata.results[].error_messageagainst itsdelivery_id.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
deliveries | array<object> | — |
deliveries.delivery_id | string | — |
deliveries.sequence | number | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/deliveries" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "deliveries": [6 {7 "delivery_id": "delivery-uuid-1",8 "sequence": 19 },10 {11 "delivery_id": "delivery-uuid-3",12 "sequence": 213 },14 {15 "delivery_id": "delivery-uuid-4",16 "sequence": 317 }18 ]19}'Response examples
1{2 "success": true,3 "message": "Trip deliveries synced successfully",4 "data": {5 "id": "trip-uuid",6 "summary": {7 "total": 38 }9 }10}Remove Orders from Trip
/delivery/trips/{{trip_id}}/remove-deliveriesAtomically removes one or more displayed orders from a trip. Requires bearer authentication plus manage:operations:tenant, manage:module:delivery, or system-admin access. Tenant scope comes from the authenticated request; Accept-Language controls the localized message.
Send one ID for single removal or multiple IDs for bulk removal.
Duplicate IDs are accepted once; the first occurrence defines response order.
Business rules
Every selected ID and its displayed membership in the source trip are validated before any write. A missing or stale member rolls back the whole request.
Every trip status and every order status are allowed.
For active, completed, or planned live membership, only
trip_idandtrip_sequenceare cleared. Order status, active assignment, assigned driver, capacity, and order history remain unchanged, so the driver continues to receive the order through normal driver-order APIs.The trip status and all unselected orders remain unchanged, including when the trip becomes empty. Maintained counts update transactionally; trip detail derives its displayed order list and stops from the remaining members.
For cancelled trips, only matching entries in
historical_deliveriesare removed. Current order membership and any newer trip or driver assignment are never changed. Existing audit history is preserved and onedeliveries_removedtrip event records this action.
Success returns HTTP 200 only after the RPC transaction commits. data.removed_delivery_ids contains the requested unique IDs in first-seen order, removed_count is its length, and summary.total is the remaining displayed order count.
Important errors all use { success:false, code, message, data:null }:
HTTP 400
delivery_ids_requiredorinvalid_uuid.HTTP 401/403 for bearer authentication or trip-management permission failure.
HTTP 404
trip_not_foundordelivery_not_found.HTTP 409
delivery_not_in_tripwhen membership changed.HTTP 500
request_processing_failedafter the database operation rolls back.
Frontend integration contract
call POST /functions/v1/delivery/trips/{trip_id}/remove-deliveries with the literal { "delivery_ids": [...] } body. Treat the response fields as required. Branch on code, display message, and make no follow-up mutation to order status or driver assignment. No navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Accept-Language | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
delivery_idsRequired | array<string> | is a required non-empty array of UUIDs. |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/remove-deliveries" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --header "Accept-Language: en" \5 --data-raw '{6 "delivery_ids": [7 "11111111-1111-4111-8111-111111111111",8 "22222222-2222-4222-8222-222222222222"9 ]10}'Response examples
1{2 "success": true,3 "message": "Orders removed from the trip successfully.",4 "data": {5 "id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",6 "removed_delivery_ids": [7 "11111111-1111-4111-8111-111111111111"8 ],9 "removed_count": 1,10 "summary": {11 "total": 712 }13 }14}Move Orders Between Trips
/delivery/trips/{{source_trip_id}}/move-deliveriesAtomically moves one or more orders from a source trip to an existing destination trip and that trip's actual driver. Requires bearer authentication plus manage:operations:tenant, manage:module:delivery, or system-admin access. Tenant scope comes only from the authenticated request; Accept-Language selects the localized display message.
Business rules
One RPC locks both trip rows in stable order, then locks every selected order, P&D preparation resource, assignment, and affected driver capacity row. Every ID, current source membership, order status, destination state, and destination driver is rechecked before the first write. Any failure rolls back the whole selection.
deliveredandcancelledorders are blocked. Other order statuses and the source trip's status are allowed. The destination must be a differentplannedorin_progresstrip with an assigned driver.The locked destination trip's actual driver must equal
target_driver_id. A mismatch returns HTTP 409target_trip_changedand changes nothing. No new driver/trip lookup API is required; use the existingGET /delivery/driversandGET /delivery/tripsselectors.Orders retain their delivery status and prior history. Existing source and destination sequences are unchanged; moved orders are appended after the destination's current maximum sequence in their source order.
When drivers differ, the current assignment, custody, capacity, P&D handoff projection, and assignment-backed driver list move to the receiving driver. A different destination trip with the same driver changes only trip membership.
Both trip lifecycle statuses remain unchanged, including an empty source. Maintained counts update inside the transaction, and trip details derive their order lists and stops from the committed membership.
For a cancelled source, displayed membership comes from its historical snapshot. Selected snapshot entries are removed only if each current order remains outside any newer trip, route, route session, or live Waybill; a newer live assignment is never silently detached.
Both trips receive append-only move events containing actor, timestamp, trimmed reason, source/target trip IDs, immutable source/target trip-number snapshots, old/new driver IDs, order IDs, and source/target sequence values.
Success returns HTTP 200 only after commit. Every response field is required. moved_delivery_ids contains exactly the requested unique IDs in first-seen order; counts are the final displayed source and destination totals.
Important errors all use { success:false, code, message, data:null }:
HTTP 400 for missing fields, malformed UUIDs, an empty selection, or a reason outside 1–500 characters.
HTTP 401/403 for bearer authentication or trip-management permission failure.
HTTP 404 for a missing source trip, destination trip, order, or required driver record.
HTTP 409 for stale source membership, blocked order status, an unavailable/same destination, missing destination driver/vehicle assignment, capacity conflict, missing current order assignment, or
target_trip_changed.HTTP 500
request_processing_failedfor a rolled-back unexpected server error.
Frontend integration contract
call POST /functions/v1/delivery/trips/{source_trip_id}/move-deliveries with the literal four-field body above. Treat every success field as required. Branch on stable code, display the localized message, and refresh selectors before reconfirming target_trip_changed. New move-history entries are available through trip detail with source_trip_number and target_trip_number; fall back to the corresponding IDs for older events. No navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
source_trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Accept-Language | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
delivery_idsRequired | array<string> | non-empty UUID array. Single and bulk moves use the same endpoint. Duplicate IDs are accepted once and returned in first-seen request order. |
target_trip_idRequired | string | destination trip UUID; it must differ from the source. |
target_driver_idRequired | string | the destination driver UUID shown in the frontend confirmation. |
reasonRequired | string | trimmed, 1–500 characters. |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${SOURCE_TRIP_ID}/move-deliveries" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --header "Accept-Language: en" \5 --data-raw '{6 "delivery_ids": [7 "11111111-1111-4111-8111-111111111111",8 "22222222-2222-4222-8222-222222222222"9 ],10 "target_trip_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",11 "target_driver_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd",12 "reason": "Correct trip assignment"13}'Response examples
1{2 "success": true,3 "message": "Orders moved successfully.",4 "data": {5 "source_trip_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",6 "target_trip_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb",7 "moved_delivery_ids": [8 "11111111-1111-4111-8111-111111111111"9 ],10 "moved_count": 1,11 "source_summary": {12 "total": 713 },14 "target_summary": {15 "total": 316 }17 }18}Remove Delivery from Trip
/delivery/trips/{{trip_id}}/deliveries/{{delivery_id}}Compatibility endpoint for removing one delivery from a trip. Requires bearer authentication plus manage:operations:tenant, manage:module:delivery, or system-admin access. It now uses the same atomic preservation workflow as POST /delivery/trips/{trip_id}/remove-deliveries: every trip and order status is allowed, only trip_id and trip_sequence are cleared, and the trip status, order status, active assignment, assigned driver, driver capacity, and histories remain unchanged. The response retains the existing trip-detail shape for compatibility. New dispatcher and tenant-admin flows should use the POST endpoint for its single/bulk contract. This endpoint does not delete the delivery.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
delivery_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/deliveries/${DELIVERY_ID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Delivery removed from trip successfully",4 "data": {5 "id": "trip-uuid",6 "summary": {7 "total": 18 }9 }10}Reorder Trip Deliveries
/delivery/trips/{{trip_id}}/reorderReorder deliveries in the trip by providing all delivery IDs in the desired order.
Must include all current trip deliveries — no missing, no extra
trip_sequencewill be reassigned as 1, 2, 3...Only allowed while trip is
plannedThe returned
driver_execution.stop_listuses the requested delivery order as a preference, but moves a grouped drop-off after every pickup required by the parcels inside itStop grouping uses only exact valid latitude/longitude coordinates; address IDs and text are ignored
Frontend integration contract
Render the returned
data.driver_execution.stop_list,current_stop, andnext_stopdirectly.Do not assume each delivery row produces adjacent pickup and drop-off stops.
No navigation behavior changes.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
delivery_ids | array<string> | — |
1curl --globoff --request PATCH "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/reorder" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "delivery_ids": [6 "delivery-uuid-2",7 "delivery-uuid-1",8 "delivery-uuid-3"9 ]10}'Response examples
1{2 "success": true,3 "message": "Trip stop order updated successfully",4 "data": {5 "id": "trip-uuid",6 "status": "planned"7 }8}Save Trip Stop Order
/delivery/trips/{{trip_id}}/stops/orderSaves the dispatcher-defined order of whole stops for a planned trip. Requires bearer authentication plus manage:operations:tenant, manage:module:delivery, or system-admin access. Send every stop_key from the latest trip detail exactly once and send that detail response's unchanged updated_at.
The database locks the trip, rechecks tenant ownership, planned status, timestamp, and the complete current stop set, then saves in one transaction. Every delivery's pickup must precede its drop-off. A pickup shared by every current order must be first; a drop-off shared by every current order must be last. Independent pickup/drop-off pairs may interleave. Invalid input is rejected without partial writes or silent rearrangement. A successful save appends one stops_reordered audit event and changes only stop_order, trip_events, and updated_at; trip status, delivery status, driver assignment, delivery membership, delivery sequence, and stop membership remain unchanged.
Trip detail for both dispatchers and drivers uses the saved order. Starting the trip preserves it. When membership or copied delivery addresses change, the trip timestamp advances; effective reads ignore obsolete keys, preserve surviving priorities, and insert new stops in a deterministic dependency-safe position. The older delivery reorder endpoint clears the custom stop order.
Important errors
HTTP 400
invalid_request: malformed trip ID/body/timestamp, duplicate or unknown keys, or an incomplete stop set.HTTP 403
insufficient_permissions: caller cannot manage trips.HTTP 404
trip_not_found: the tenant-scoped trip does not exist.HTTP 409
trip_changed:expected_updated_atis stale.HTTP 409
trip_not_reorderable: the trip is no longer planned.HTTP 422
invalid_stop_order: pickup/drop-off dependency or shared-boundary rule failed.HTTP 500
save_failed: the transaction rolled back because of an unexpected server error.
Frontend integration contract
drag locally and call this endpoint only on Apply. On HTTP 200, replace the Stops tab with data.driver_execution and store data.updated_at. On trip_changed, keep the draft but require Reset to latest before another Apply. For all failures, branch on code and display the localized message. No navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
ordered_stop_keys | array<string> | — |
expected_updated_at | string | — |
1curl --globoff --request PATCH "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/stops/order" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "ordered_stop_keys": [6 "pickup:36.19,44.01",7 "dropoff:36.2,44.02",8 "pickup:36.21,44.03",9 "dropoff:36.22,44.04"10 ],11 "expected_updated_at": "2026-09-10T10:00:00.000Z"12}'Response examples
1{2 "success": true,3 "message": "Stop order updated.",4 "data": {5 "trip_id": "source-trip-uuid",6 "status": "planned",7 "updated_at": "2026-09-10T10:05:00.000Z",8 "driver_execution": {9 "stop_list": [10 {11 "stop_key": "pickup:36.19,44.01",12 "stop_type": "pickup",13 "location_label": null,14 "location": {15 "latitude": 36.19,16 "longitude": 44.0117 },18 "deliveries": [19 {20 "delivery_id": "order-uuid-1",21 "delivery_code": "3341A",22 "barcode": "DEL3341A",23 "status": "assigned",24 "trip_sequence": 1,25 "assignment_status": "assigned",26 "next_action": "start_delivery",27 "bulk_eligible": true28 }29 ],30 "is_complete": false31 }32 ],33 "current_stop": {34 "stop_key": "pickup:36.19,44.01",35 "stop_type": "pickup",36 "location_label": null,37 "location": {38 "latitude": 36.19,39 "longitude": 44.0140 },41 "deliveries": [42 {43 "delivery_id": "order-uuid-1",44 "delivery_code": "3341A",45 "barcode": "DEL3341A",46 "status": "assigned",47 "trip_sequence": 1,48 "assignment_status": "assigned",49 "next_action": "start_delivery",50 "bulk_eligible": true51 }52 ],53 "is_complete": false54 },55 "next_stop": null56 }57 }58}Cancel Trip
/delivery/trips/{{trip_id}}/cancelCancels a planned or in_progress trip only when every current delivery is still pending, assigned, or en_route_to_pickup. Any picked-up, in-transit, delivered, cancelled, returned, exception, or otherwise non-cancellable member blocks the whole action. Accepts optional reason and optional notes (each max 2000 characters) and requires trip-management permission.
The operation is atomic
the trip becomes cancelled; status history is appended; a trip_cancelled actor event is appended; the ordered delivery snapshot is stored on the trip; active assignments become unassigned; driver capacity is released; deliveries return to pending; and live trip fields/stops are cleared. Repeating cancellation is idempotent and does not append another event or notification. On a real cancellation, the assigned driver receives one critical in-app modal requiring acknowledgement plus push notification; the notification tells the driver not to continue and links to the trip.
Frontend integration contract
after success, load GET /delivery/trips/{id}. Render the cancellation story from data.trip_events; render data.deliveries as read-only when data.deliveries_mode=historical. current_stop, next_stop, and stop_list remain empty.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
reason | string | — |
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/cancel" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "reason": "Trip plan cancelled before pickup",6 "notes": "Vehicle issue reported before pickup"7}'Response examples
1{2 "success": true,3 "message": "Trip cancelled successfully",4 "data": {5 "id": "trip-uuid",6 "previous_status": "in_progress",7 "status": "cancelled",8 "changed": true,9 "started_at": "2026-07-29T12:30:00Z",10 "completed_at": null,11 "released_delivery_count": 3,12 "released_assignment_count": 3,13 "available_actions": []14 }15}Driver: List My Trips
/delivery/driver-tripsDriver-facing
list trips assigned to the authenticated driver.
Driver sees trip_number, status, item_count, and timestamps. They can then call GET /delivery/trips/:id to get the full stop list for execution. The response is bounded to the requested page (maximum 100); item_count and the all-status summary are database-maintained/aggregated and do not load every matching row into the API runtime.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number Example: |
limit | string | Items per page (max 100) Example: |
status | string | Filter by status Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/driver-trips?page=1&limit=20&status=in_progress" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Trips retrieved successfully",4 "data": {5 "trips": [6 {7 "id": "trip-uuid",8 "trip_number": "TRIP-00001",9 "status": "in_progress",10 "trip_type": "multi_pickup",11 "item_count": 3,12 "started_at": "2026-04-11T08:00:00Z",13 "completed_at": null14 }15 ],16 "total": 1,17 "page": 1,18 "limit": 2019 }20}Get Trip Partial Returns
/delivery/trips/{{trip_id}}/returnsConvenience endpoint — returns only the deliveries in this trip that have one or more return requests.
Useful for a focused 'returns' tab on the trip detail screen without loading the full trip payload.
Each item includes
delivery_id,delivery_code, barcode,delivery_status,return_status,trip_sequencereturn_requestsarray with id, status,return_reason,return_delivery_id,requested_at, notes
Note
return requests are created via the existing POST /delivery/returns endpoint — no new endpoint needed for creation.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/returns" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Trip returns retrieved successfully",4 "data": [5 {6 "delivery_id": "uuid-d2",7 "delivery_code": 1002,8 "barcode": "BC002",9 "delivery_status": "returned",10 "return_status": "return_approved",11 "trip_sequence": 2,12 "return_requests": [13 {14 "id": "rr-uuid",15 "status": "approved",16 "return_reason": "Customer refused delivery",17 "return_delivery_id": "uuid-return-delivery",18 "requested_at": "2026-04-11T10:30:00Z",19 "notes": null20 }21 ]22 },23 {24 "delivery_id": "uuid-d5",25 "delivery_code": 1005,26 "barcode": "BC005",27 "delivery_status": "returned",28 "return_status": "return_requested",29 "trip_sequence": 5,30 "return_requests": [31 {32 "id": "rr-uuid-2",33 "status": "pending",34 "return_reason": "Wrong address",35 "return_delivery_id": null,36 "requested_at": "2026-04-11T11:00:00Z",37 "notes": null38 }39 ]40 }41 ]42}Driver: Execute Trip Stop Bulk Action
/delivery/trips/{{trip_id}}/stops/bulk-actionDriver-facing group action for selected deliveries at one derived trip stop. The authenticated user must have the driver role, must be online/active/available, and must be the trip's assigned driver. Send the exact stop_key returned by GET trip detail plus 1 to 200 unique delivery UUIDs. Every ID is revalidated against tenant, trip, stop, driver assignment, current action, exceptions, returns, terminal state, carrier, and active Waybill rules; client selection is never trusted as authorization.
Actions
start_delivery: pickup stop;assignedoracceptedassignment becomesen_route_to_pickup, using the same canonical transition and side effects as the individual sheet.pickup: pickup stop; proofless single-parcel deliveries becomepicked_up. A delivery needing pickup proof or parcel review returnsoutcome=individual_action_requiredwithout mutation.in_transit: drop-off stop;picked_upbecomes assignmenten_route_to_deliveryand deliveryin_transit.complete: drop-off stop; every valid delivery returnsoutcome=individual_action_requiredwithout mutation so the app opens that order's existing completion/proof screen. No proof fields are accepted by this endpoint and proof is never shared.
The valid request returns HTTP 200 even when individual orders fail. data.results remains in trip sequence order and each row contains delivery ID/code, outcome (applied, individual_action_required, or failed), localized message, machine code when relevant, and authoritative status fields. Successful orders are not rolled back when another order fails. Fixed validation reads are batched. Eligible pickups are sent through one service-role database RPC; that RPC invokes the canonical guarded transition in request order and isolates expected per-delivery business failures. Unexpected database faults abort the outer transaction. Already-paid deliveries cause no payment write, while unpaid zero-fee deliveries use one set-based status update; positive sender-cash fees keep the existing idempotent COD/payment owner. Other actions retain bounded concurrency 10. Trip navigation does not fan out external route-calculation requests per order.
Important request errors
HTTP 400: missing stop key, invalid action, invalid/duplicate IDs, more than 200 IDs, or invalid UUID.
HTTP 403: not a driver, trip belongs to another driver, or driver is offline/inactive.
HTTP 404: trip or driver not found.
HTTP 409: trip is no longer planned/in progress.
Frontend integration contract
Use
data.driver_execution.stop_list[].stop_key. Show selection only for deliveries whose literalnext_actionmatches the button action.Send one common action and selected delivery IDs. The button text includes the selection count, for example
Start 7 deliveries.Apply
outcome=applied, display every failed delivery bydelivery_code, and continue processing the remaining rows.For
individual_action_required, open each delivery's existing pickup or completion screen sequentially in result order. Refresh the trip once after the group/queue finishes.Keep individual-card actions and per-order exception actions available. Branch on codes, never localized message text. No navigation action is returned by the backend.
Event actor contract
new driver-performed status events use the validated assigned drivers.id as actor_id, including grouped pickup. The authenticated user ID remains available to backend authorization and custody logic; clients do not send an actor ID.
Path parameters
| Parameter | Type | Description |
|---|---|---|
trip_idRequired | string | Variable used inside the request path. |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
stop_key | string | — |
action | string | — |
delivery_ids | array<string> | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/trips/${TRIP_ID}/stops/bulk-action" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "stop_key": "pickup:33.3,44.4",6 "action": "start_delivery",7 "delivery_ids": [8 "00000000-0000-4000-8000-000000000001",9 "00000000-0000-4000-8000-000000000002"10 ]11}'Response examples
1{2 "success": true,3 "message": "Trip stop action completed",4 "data": {5 "trip_id": "trip-uuid",6 "stop_key": "dropoff:33.3,44.4",7 "action": "complete",8 "results": [9 {10 "delivery_id": "00000000-0000-4000-8000-000000000001",11 "delivery_code": "ORD-1",12 "outcome": "individual_action_required",13 "success": true,14 "previous_assignment_status": "en_route_to_delivery",15 "assignment_status": "en_route_to_delivery",16 "delivery_status": "in_transit",17 "code": "trip_stop_individual_action_required",18 "message": "Continue with this delivery individually to review parcels or add proof"19 },20 {21 "delivery_id": "00000000-0000-4000-8000-000000000002",22 "delivery_code": "ORD-2",23 "outcome": "failed",24 "success": false,25 "previous_assignment_status": "en_route_to_delivery",26 "assignment_status": "en_route_to_delivery",27 "delivery_status": "in_transit",28 "code": "delivery_has_open_exception",29 "message": "Resolve the active delivery exception before continuing"30 }31 ],32 "requested_count": 2,33 "applied_count": 0,34 "individual_action_required_count": 1,35 "failed_count": 136 }37}Driver: Trip Status Summary
/delivery/driver-trips/status-summaryDriver-facing
returns database-aggregated counts per trip status for the authenticated driver without loading individual trip rows into the API runtime.
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/driver-trips/status-summary" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Trip status summary retrieved successfully",4 "data": {5 "planned": 2,6 "in_progress": 1,7 "completed": 15,8 "cancelled": 09 }10}