Routes
16 API calls in this section.
Route feature — reusable named delivery lanes from origin to destination.
A Route is a fixed lane. Its optional driver_id is reserved only for automatic scheduled-session creation and does not own the route. A Route Session is one operational run and owns exactly one immutable driver. Different sessions of the same route may use different drivers.
Session lifecycle
open → started → picked_up → in_transit → completed (auto) | cancelled
Route
driver_id,schedule_time,schedule_days, andstart_datemust be provided together or all cleared.Changing the route driver affects future scheduled sessions only. Existing sessions never change driver.
Manual assign-deliveries requests use their explicit
driver_idorsession_idand never infer the route driver.driver_idassignment mode reuses the newest open or started session for the same route and driver; it creates a new session only when no accepting session exists.session_idmode adds deliveries only to that exact open/started session.A session driver cannot be changed by any role. Replacement is cancel the pre-pickup session and create a new session with the correct driver.
Drivers see only sessions assigned to their own driver record; route list/detail are dispatcher-only.
A driver cannot start an empty session; at least one active non-terminal delivery must be assigned.
Any session action that moves or resets deliveries is blocked when one of the session deliveries has an open delivery exception; resolve the exception first.
Route success, validation, conflict, and failure messages are localized for English, Arabic, Kurdish, and Persian through the existing
languagequery parameter orAccept-Languageheader.
List Routes
/delivery/routesDispatcher-only list of reusable route lanes. Excludes inactive routes by default. Each row includes optional driver_id and compact driver scheduling configuration plus read-only next_session_at. The route driver is not operational ownership. Driver users obtain route context through their own /delivery/route-sessions rows.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number Example: |
limit | string | Items per page (max 100) Example: |
search | string | Partial match on |
include_inactive | string | Include soft-deleted routes Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/routes?page=1&limit=20&search=&include_inactive=false" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Routes retrieved successfully",4 "data": {5 "routes": [6 {7 "id": "route-uuid",8 "route_name": "Warehouse A → Zone 5",9 "driver_id": "driver-uuid",10 "driver": {11 "id": "driver-uuid",12 "full_name": "Scheduled Driver",13 "avatar_url": null14 },15 "origin_type": "warehouse",16 "origin_warehouse_id": "wh-uuid",17 "destination_type": "location",18 "destination_location": {19 "lat": 33.5,20 "lng": 44.2,21 "address": "Zone 5 Hub"22 },23 "is_active": true,24 "schedule_time": "08:00:00",25 "schedule_days": [26 1,27 2,28 3,29 4,30 531 ],32 "start_date": "2026-04-15",33 "next_session_at": "2026-04-15T05:00:00Z",34 "notes": null,35 "created_at": "2026-04-14T10:00:00Z",36 "active_orders_count": 837 }38 ],39 "total": 1,40 "page": 1,41 "limit": 2042 }43}Create Route
/delivery/routesCreate a reusable route lane.
origin_type/destination_type: warehouse | locationIf type is
warehouse: provide*_warehouse_idIf type is
location: provide*_locationas{lat, lng, address}driver_idis optional and is used only to create future sessions automatically from this route's schedule. It does not own the route and is ignored by manual assign-deliveries requests.driver_id,schedule_time,schedule_days, andstart_datemust be provided together or all omitted/null.The driver must be active and belong to the authenticated tenant.
Scheduled creation uses the tenant IANA timezone and returns read-only
next_session_at.
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
route_name | string | — |
origin_type | string | — |
origin_warehouse_id | string | — |
destination_type | string | — |
destination_location | object | — |
destination_location.lat | number | — |
destination_location.lng | number | — |
destination_location.address | string | — |
driver_id | string | — |
schedule_time | string | — |
schedule_days | array<number> | — |
start_date | string | — |
Show 1 more body fields
Additional body parameters
| Parameter | Type | Description |
|---|---|---|
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/routes" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "route_name": "Warehouse A → Zone 5",6 "origin_type": "warehouse",7 "origin_warehouse_id": "wh-uuid",8 "destination_type": "location",9 "destination_location": {10 "lat": 33.5,11 "lng": 44.2,12 "address": "Zone 5 Hub"13 },14 "driver_id": "driver-uuid",15 "schedule_time": "08:00:00",16 "schedule_days": [17 1,18 2,19 3,20 4,21 522 ],23 "start_date": "2026-04-15",24 "notes": "Morning route"25}'Response examples
1{2 "success": true,3 "message": "Route created successfully",4 "data": {5 "id": "route-uuid",6 "route_name": "Warehouse A → Zone 5",7 "driver_id": "driver-uuid",8 "driver": {9 "id": "driver-uuid",10 "full_name": "Scheduled Driver",11 "avatar_url": null12 },13 "is_active": true,14 "schedule_time": "08:00:00",15 "schedule_days": [16 1,17 2,18 3,19 4,20 521 ],22 "start_date": "2026-04-15",23 "next_session_at": "2026-04-15T05:00:00Z",24 "created_at": "2026-04-14T10:00:00Z"25 }26}Get Route
/delivery/routes/:routeIdDispatcher-only route detail. Returns the fixed lane, optional reserved scheduling driver_id and compact driver, read-only next_session_at, warehouse/location information, and aggregate session_counts. It does not return an ambiguous singular current session.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route retrieved successfully",4 "data": {5 "id": "route-uuid",6 "route_name": "Warehouse A → Zone 5",7 "driver_id": "driver-uuid",8 "driver": {9 "id": "driver-uuid",10 "full_name": "Scheduled Driver",11 "avatar_url": null12 },13 "origin_type": "warehouse",14 "origin_warehouse": {15 "id": "wh-uuid",16 "name": "Warehouse A",17 "code": "WH-001"18 },19 "destination_type": "location",20 "destination_location": {21 "lat": 33.5,22 "lng": 44.2,23 "address": "Zone 5 Hub"24 },25 "schedule_time": "08:00:00",26 "schedule_days": [27 1,28 2,29 3,30 4,31 532 ],33 "start_date": "2026-04-15",34 "next_session_at": "2026-04-15T05:00:00Z",35 "session_counts": {36 "active": 2,37 "completed": 1,38 "total": 339 },40 "is_active": true41 }42}Update Route
/delivery/routes/:routeIdPartial route-lane update. Route driver_id is optional reserved configuration used only for future scheduled sessions. Changing it never changes an existing session. When any scheduling field is changed, the resulting driver_id, schedule_time, schedule_days, and start_date must all be present or all null. To disable scheduling, send all four fields as null in the same request. Manual assign-deliveries behavior is unchanged.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
route_name | string | — |
notes | string | — |
driver_id | string | — |
schedule_time | string | — |
schedule_days | array<number> | — |
start_date | string | — |
1curl --globoff --request PUT "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "route_name": "Warehouse A → Zone 6",6 "notes": "Updated route",7 "driver_id": "driver-uuid",8 "schedule_time": "07:00:00",9 "schedule_days": [10 1,11 3,12 513 ],14 "start_date": "2026-04-15"15}'Response examples
1{2 "success": true,3 "message": "Route updated successfully",4 "data": {5 "id": "route-uuid",6 "route_name": "Warehouse A → Zone 6",7 "driver_id": "driver-uuid",8 "driver": {9 "id": "driver-uuid",10 "full_name": "Scheduled Driver",11 "avatar_url": null12 },13 "schedule_time": "07:00:00",14 "schedule_days": [15 1,16 3,17 518 ],19 "start_date": "2026-04-15",20 "next_session_at": "2026-04-15T04:00:00Z",21 "updated_at": "2026-04-14T11:00:00Z"22 }23}Delete Route (Deactivate)
/delivery/routes/:routeIdSoft-deactivate a route (sets is_active=false). Rejected if the route has open/started sessions with active deliveries.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request DELETE "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route deactivated successfully",4 "data": null5}Assign Deliveries to Route
/delivery/routes/:routeId/assign-deliveriesPrepared P&D/operational-return deliveries use the same assignment owner as direct assignment. In the route transaction, each eligible row keeps its completed pick and reserved warehouse stock and receives one pending handoff for the route driver. Waybill and unrelated deliveries keep their existing route behavior. Realtime mobile contract: the existing consolidated delivery_realtime_assignments row emits assignment_status: "assigned" and requires_driver_response: false. Mobile keeps the current informational notification and does not show Accept/Reject. Missing or unknown actionability is non-actionable.
Assign deliveries to a route. The operation is all-or-nothing and accepts at most 100 UUIDs.
Exactly one session mode is required
driver_id: reuses the newest open or started session for the same route and driver and returnsassignment_outcome: existing_session; if none exists, it intentionally creates one open session and returnsnew_session_created.session_id: appends to that exact session when it is open or started and returnsexisting_session. If it is past started or otherwise cannot accept orders, the backend creates exactly one replacement session for the batch and returnsfallback_session_created.
For a fallback, requested_session identifies what the dispatcher requested, session identifies where every delivery in the successful batch was assigned, and fallback_reason.code is authoritative. In driver_id mode, requested_session is always null because no session ID was requested; session is the reused or newly created target. Supported reason codes are SESSION_NOT_ACCEPTING_ORDERS, SESSION_COMPLETED, SESSION_CANCELLED, and SESSION_ROUTE_MISMATCH. If the immutable driver cannot be used, HTTP 409 returns SESSION_DRIVER_UNAVAILABLE and no replacement session remains.
The selected driver must be active/available, have a primary active vehicle, have route-zone access, and have enough capacity. Deliveries must be pending internal child/standalone deliveries with no trip, Waybill, route, route session, or active assignment. The route/session locks, one optional session insert, batch assignments, delivery membership, and capacity update commit atomically in one database transaction; complete failure rolls everything back. Realtime and push notification remain best effort after commit.
Frontend integration contract
Send
{delivery_ids, driver_id}to reuse-or-create for that route and driver, or{delivery_ids, session_id}to request an exact session. Never send both.Branch only on
data.assignment_outcome.existing_sessionmay haverequested_session: nullwhen the request useddriver_id; always usedata.sessionas the actual target. Forfallback_session_created, show the localizeddata.fallback_reason.message, name/linkdata.session, and retaindata.requested_sessiononly as context.Do not compare IDs or parse localized message text.
data.fallback_reason.codeis the stable reason contract.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
delivery_ids | array<string> | — |
driver_id | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}/assign-deliveries" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "delivery_ids": [6 "delivery-uuid-1",7 "delivery-uuid-2",8 "delivery-uuid-3"9 ],10 "driver_id": "driver-uuid"11}'Response examples
1{2 "success": true,3 "message": "Orders assigned successfully",4 "data": {5 "assignment_outcome": "fallback_session_created",6 "requested_session": {7 "id": "requested-session-uuid",8 "session_number": 1,9 "status": "in_transit"10 },11 "session": {12 "id": "new-session-uuid",13 "session_number": 2,14 "status": "open",15 "route_id": "route-uuid",16 "driver_id": "driver-uuid",17 "tenant_id": "tenant-uuid",18 "created_at": "2026-08-06T10:00:00Z",19 "started_at": null,20 "updated_at": "2026-08-06T10:00:00Z",21 "cancelled_at": null,22 "completed_at": null,23 "picked_up_at": null,24 "proof_requirements": null,25 "proof_of_pickup": null,26 "proof_of_delivery": null,27 "driver": {28 "id": "driver-uuid",29 "full_name": "Ahmed Hassan",30 "avatar_url": null31 }32 },33 "fallback_reason": {34 "code": "SESSION_NOT_ACCEPTING_ORDERS",35 "message": "The requested session can no longer accept orders."36 },37 "assigned_count": 238 }39}Remove Deliveries from Route
/delivery/routes/:routeId/remove-deliveriesRemove deliveries from the route session. Only allowed if the session is open or started. The submitted batch is one transaction. Each current assignment is preserved as unassigned audit history, route/session membership is cleared, delivery status returns to pending, and driver capacity is released. For prepared P&D work, the completed pick and stock reservation remain; the same pending handoff keeps its identity but clears assignment and driver context until reassignment.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Content-Type | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
delivery_ids | array<string> | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}/remove-deliveries" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "delivery_ids": [6 "delivery-uuid-1"7 ]8}'Response examples
1{2 "success": true,3 "message": "Deliveries removed from route successfully",4 "data": {5 "removed_count": 16 }7}List Sessions for Route
/delivery/routes/:routeId/sessionsDispatcher view — paginated sessions for a route ordered newest first. Every session includes its immutable driver_id and driver summary. The statuses matrix is unaffected by the active status filter. orders_count is current membership for active sessions or snapshot size for cancelled sessions; released_orders_count reports released cancellation history.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter: open | started | |
page | string | Page number (default: 1) Example: |
limit | string | Items per page (default: 20, max: 100) Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}/sessions?status=&page=1&limit=20" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route sessions retrieved successfully",4 "data": {5 "sessions": [6 {7 "id": "session-uuid-2",8 "session_number": 2,9 "status": "open",10 "driver_id": "driver-b",11 "driver": {12 "id": "driver-b",13 "full_name": "Driver B",14 "avatar_url": null15 },16 "orders_count": 3,17 "released_orders_count": 0,18 "created_at": "2026-04-14T05:00:00Z"19 },20 {21 "id": "session-uuid",22 "session_number": 1,23 "status": "completed",24 "driver_id": "driver-a",25 "driver": {26 "id": "driver-a",27 "full_name": "Driver A",28 "avatar_url": null29 },30 "orders_count": 4,31 "released_orders_count": 0,32 "created_at": "2026-04-13T07:55:00Z"33 }34 ],35 "statuses": [36 {37 "status": "open",38 "label": "Open",39 "count": 140 },41 {42 "status": "completed",43 "label": "Completed",44 "count": 145 }46 ],47 "total": 2,48 "page": 1,49 "limit": 2050 }51}List Driver Route Sessions
/delivery/route-sessionsDriver-facing. Driver-role callers receive only sessions whose immutable driver_id matches their authenticated driver record; query parameters cannot widen that scope. Dispatcher-only callers can list all tenant sessions and filter by driver_id or route_id. Rows include route context, immutable driver_id, driver summary, and order counts.
Query parameters
| Parameter | Type | Description |
|---|---|---|
status | string | Filter: open | started | |
page | string | Page number (default: 1) Example: |
limit | string | Items per page (default: 20, max: 100) Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/route-sessions?status=&page=1&limit=20" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route sessions retrieved successfully",4 "data": {5 "sessions": [6 {7 "id": "session-uuid",8 "session_number": 2,9 "status": "started",10 "driver_id": "driver-uuid",11 "driver": {12 "id": "driver-uuid",13 "full_name": "Ahmed Hassan",14 "avatar_url": null15 },16 "orders_count": 4,17 "released_orders_count": 0,18 "created_at": "2026-04-14T05:00:00Z",19 "route": {20 "id": "route-uuid",21 "route_name": "Warehouse A → Zone 5",22 "origin_type": "warehouse",23 "destination_type": "location",24 "destination_location": {25 "lat": 33.5,26 "lng": 44.2,27 "address": "Zone 5 Hub"28 }29 }30 }31 ],32 "statuses": [33 {34 "status": "started",35 "label": "Started",36 "count": 137 }38 ],39 "total": 1,40 "page": 1,41 "limit": 2042 }43}Get Route Session Detail
/delivery/route-sessions/:sessionIdReturns session detail with route info and summary. This endpoint does not include delivery rows or the raw cancellation order snapshot.
session.proof_requirements is the stored strictest requirement calculated when deliveries are added or removed. session.proof_of_pickup and session.proof_of_delivery expose the shared proof after capture for both driver and dispatcher/tenant-admin screens.
For a cancelled session, session.cancellation contains cancelled_at, reason, and the authenticated cancelled_by actor snapshot. session.orders_count and session.released_orders_count and summary.total come from the immutable cancellation snapshot. Its collection summary is zero because released orders no longer belong operationally to that run.
Driver-role callers can access only sessions assigned to their own driver record. Dispatcher-only callers can access any session in the tenant.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route session retrieved successfully",4 "data": {5 "session": {6 "id": "session-uuid",7 "session_number": 2,8 "status": "started",9 "driver_id": "driver-uuid",10 "driver": {11 "id": "driver-uuid",12 "full_name": "Ahmed Hassan",13 "avatar_url": null14 },15 "started_at": "2026-04-14T05:05:00Z",16 "picked_up_at": null,17 "proof_requirements": {18 "pickup": {19 "photo_required": true,20 "signature_required": false21 },22 "delivery": {23 "photo_required": true,24 "signature_required": true25 }26 },27 "proof_of_pickup": null,28 "proof_of_delivery": null29 },30 "route": {31 "id": "route-uuid",32 "route_name": "Warehouse A → Zone 5",33 "origin_type": "warehouse",34 "destination_type": "location"35 },36 "summary": {37 "total": 5,38 "status_breakdown": {39 "assigned": 3,40 "picked_up": 241 },42 "collection_summary": {43 "total_to_collect": 15000,44 "pickup_total_to_collect": 0,45 "delivery_total_to_collect": 15000,46 "deliveries_to_collect_count": 1,47 "delivery_collection_required_count": 1,48 "requires_collection_confirmation": true49 }50 }51 }52}List Route Session Deliveries
/delivery/route-sessions/:sessionId/deliveriesPaginated deliveries for a route session. Driver-role callers can access only sessions assigned to their own driver record; dispatcher-only callers can access any tenant session. Search is a case-insensitive partial match over delivery code, barcode, and external reference.
Active sessions return currently linked deliveries with released_orders_count=0. Cancelled sessions return immutable snapshot rows in the same deliveries array. A released historical row has status=pending (its release-time operational state), status_at_cancellation, session_membership_status=released, and released_at; it is not labelled cancelled unless the delivery itself was genuinely cancelled. orders_count is the unfiltered session membership total, while total is the filtered result count. Released-session collection totals are zero. Existing page, limit, status, and search behavior remains available.
G2G origin display
every live data.deliveries[] row includes nullable source_transfer_manifest_id and g2g_origin. A non-null origin contains { "branchName": string|null, "branchAddress": string|null } from a valid saved manifest_origin_v1 snapshot. A staged child uses its own usable snapshot first, then its same-tenant, non-deleted parent only when the child manifest ID is absent or matches the parent. Origin fields are inherited together; partial fields are not combined from different snapshots. Normal orders return null for both fields. A known manifest-linked order with no usable saved origin keeps its manifest ID and returns g2g_origin=null. Frontend integration: show a separate read-only Original G2G location line for known manifest orders, displaying any genuine branch name/address available or Origin unavailable when origin is null. Hide it for non-G2G orders. Keep pickup/delivery locations, customer, route navigation, collection amounts, actions, pagination, and totals unchanged. The existing paginated delivery query reads the origin snapshot and parent together; no per-card lookup is needed. Cancelled-session historical snapshots do not acquire these fields.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number (default: 1) Example: |
limit | string | Items per page (default: 50, max: 100) Example: |
statusOptional | string | Optional delivery status filter, including the terminal |
searchOptional | string | Optional search by |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}/deliveries?page=1&limit=50&status=&search=" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route session deliveries retrieved successfully",4 "data": {5 "deliveries": [6 {7 "id": "del-uuid",8 "delivery_code": "3213",9 "barcode": "DEL3213",10 "status": "assigned",11 "payment_method": "cod",12 "payer_type": "receiver",13 "cod_amount": 10000,14 "delivery_fee": 5000,15 "feeToCollectAtPickup": 0,16 "feeToCollectAtDelivery": 15000,17 "amountToCollect": 15000,18 "source_transfer_manifest_id": "manifest-uuid",19 "g2g_origin": {20 "branchName": "Original branch",21 "branchAddress": "Original branch address"22 }23 }24 ],25 "orders_count": 1,26 "released_orders_count": 0,27 "summary": {28 "total_to_collect": 15000,29 "pickup_total_to_collect": 0,30 "delivery_total_to_collect": 15000,31 "deliveries_to_collect_count": 1,32 "delivery_collection_required_count": 1,33 "requires_collection_confirmation": true34 },35 "proof_requirements": {36 "pickup": {37 "photo_required": true,38 "signature_required": false39 },40 "delivery": {41 "photo_required": true,42 "signature_required": true43 }44 },45 "proof_of_pickup": null,46 "proof_of_delivery": null,47 "total": 1,48 "page": 1,49 "limit": 50,50 "has_more": false51 }52}Start Session (Driver)
/delivery/route-sessions/:sessionId/startDriver action. Transitions session: open → started and sets started_at. The session must contain at least one active non-terminal delivery.
Returns 409 session_has_no_deliveries when the session is empty. Blocked with delivery_has_open_exception when any non-terminal delivery in the session has an open exception.
Frontend integration contract
Disable the Start action when the session delivery total is
0.If the API returns HTTP 409 with the localized empty-session message, keep the session open and show that message.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}/start" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Route session started successfully",4 "data": {5 "id": "session-uuid",6 "status": "started",7 "started_at": "2026-04-14T08:05:00Z"8 }9}Pickup Session (Driver)
/delivery/route-sessions/:sessionId/pickupDriver action. Transitions session: started → picked_up and keeps one pickup button for the route session.
Read proof_requirements.pickup from the session response. Submit photo_urls when photo_required=true and signature_url when signature_required=true; notes is optional. If both flags are false, the request body may be empty and pickup remains one-click.
The shared proof is stored in route_sessions.proof_of_pickup, copied to deliveries.proof_of_pickup for every included delivery, and normalized photo/signature rows are created in delivery_proofs.
For lifecycle-managed P&D/operational-return members, the route action uses the same atomic warehouse Pickup owner: existing preparation and current assignment/handoff are required; stock leaves the warehouse, a pick movement is recorded, custody becomes in_transit, and delivery/assignment/handoff/picking statuses advance together. Ordinary members keep the established route transition. A session with no eligible deliveries still preserves the existing session pickup behavior. Pickup conflicts return HTTP 409 with the same localized stable code as individual Pickup, including pickup_picking_order_required, pickup_handoff_required, delivery_has_open_exception, warehouse_item_exception_open, and pickup_stock_unavailable; the complete session transaction remains unchanged.
Payment behavior
each zero-fee delivery is marked paid without a payment transaction. A sender-paid cash delivery records its fee-only pickup collection and delivery-fee payment exactly once. Retrying after the session reached picked_up repairs any missing payment side effect without changing the session again.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
signature_url | string | — |
photo_urls | array<string> | — |
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}/pickup" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "signature_url": "https://example.com/signature.png",6 "photo_urls": [7 "https://example.com/pickup.jpg"8 ],9 "notes": "Shared route pickup proof"10}'Response examples
1{2 "success": true,3 "message": "Route session picked up successfully",4 "data": {5 "id": "session-uuid",6 "status": "picked_up",7 "picked_up_at": "2026-04-14T08:20:00Z",8 "proof_requirements": {9 "pickup": {10 "photo_required": true,11 "signature_required": false12 },13 "delivery": {14 "photo_required": true,15 "signature_required": true16 }17 },18 "proof_of_pickup": {19 "signature": null,20 "photos": [21 "https://example.com/pickup.jpg"22 ],23 "notes": "Shared route pickup proof"24 },25 "proof_of_delivery": null26 }27}Deliver Session (Driver)
/delivery/route-sessions/:sessionId/deliverDriver action. Transitions outstanding session deliveries to delivered with one shared route-session proof capture.
Read proof_requirements.delivery from the session response. Submit photo_urls when photo_required=true and signature_url when signature_required=true; notes is optional. If both flags are false, the request body may be empty.
The shared proof is stored in route_sessions.proof_of_delivery, copied to deliveries.proof_of_delivery for every included delivery, and normalized photo/signature rows are created in delivery_proofs.
Payment behavior
receiver cash combines goods COD and delivery fee in one physical dropoff collection; receiver wallet charges the receiver at completion; sender wallet was settled at creation; sender cash was settled at pickup; zero-fee deliveries are paid automatically. COD audit/payout and one driver earning per delivered child are idempotent. Retrying a completed session repairs missing payment or earning side effects without another charge.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
signature_url | string | — |
photo_urls | array<string> | — |
notes | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}/deliver" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "signature_url": "https://example.com/signature.png",6 "photo_urls": [7 "https://example.com/delivery.jpg"8 ],9 "notes": "Shared route delivery proof"10}'Response examples
1{2 "success": true,3 "message": "Route session delivered successfully",4 "data": {5 "id": "session-uuid",6 "status": "completed",7 "completed_at": "2026-04-14T10:00:00Z",8 "proof_requirements": {9 "pickup": {10 "photo_required": true,11 "signature_required": false12 },13 "delivery": {14 "photo_required": true,15 "signature_required": true16 }17 },18 "proof_of_pickup": {19 "signature": null,20 "photos": [21 "https://example.com/pickup.jpg"22 ]23 },24 "proof_of_delivery": {25 "signature": "https://example.com/signature.png",26 "photos": [27 "https://example.com/delivery.jpg"28 ],29 "notes": "Shared route delivery proof"30 }31 }32}List Eligible Deliveries for Route
/delivery/routes/:routeId/eligible-deliveriesReturns paginated deliveries eligible for route assignment.
Eligibility criteria
status is
pendingandcarrier_typeisinternal.trip_id,waybill_id,route_id, androute_session_idare all null.P&D parent orders are excluded; only operational child legs can appear.
Search behavior
searchperforms a case-insensitive partial match againstdelivery_codeandexternal_reference_id.PostgREST search control characters are escaped, so
%,_, backslashes, quotes, commas, and parentheses are treated as literal text.
Frontend integration contract
use this endpoint for the route picker, but treat assignment-time HTTP 409 results as authoritative because eligibility is revalidated atomically. No navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
routeIdRequired | string | Example: |
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | string | Page number Example: |
limit | string | Items per page (max 100) Example: |
search | string | Search by |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
1curl --globoff --request GET "https://staging-api.ondi.io/functions/v1/delivery/routes/${ROUTEID}/eligible-deliveries?page=1&limit=20&search=" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}"Response examples
1{2 "success": true,3 "message": "Eligible deliveries retrieved successfully",4 "data": {5 "deliveries": [6 {7 "id": "del-uuid",8 "delivery_code": 1865,9 "barcode": "BAR1865",10 "external_reference_id": "ORD-001",11 "status": "pending",12 "unit_size": 1,13 "pickup_location": {14 "address": "Erbil, 60m Street"15 },16 "delivery_location": {17 "address": "Sulaymaniyah, Salim St"18 },19 "customer": {20 "id": "cust-uuid",21 "full_name": "Ahmad Karimi",22 "phone": "+9647501111111"23 }24 }25 ],26 "total": 24,27 "page": 1,28 "limit": 2029 }30}Cancel Session (Dispatcher)
/delivery/route-sessions/:sessionId/cancelDispatcher action. Requires a non-empty reason of at most 500 characters. The cancelling actor is always resolved from the authenticated request and cannot be supplied by the client. Cancels an open or started session; picked_up, in_transit, completed, and already-cancelled sessions are rejected.
In one atomic transaction, the backend snapshots the cancellation actor, reason, timestamp, and session order grid fields; marks active assignments unassigned with the route_session_released audit context; releases non-terminal orders to pending with route/session links cleared; releases driver capacity; and marks the session cancelled. The marked release is permitted by the route ownership guard, while generic assignment unassign and driver-switch operations remain blocked. The historical snapshot survives later reassignment.
Blocked with delivery_has_open_exception when any non-terminal delivery in the session has an open exception.
Frontend integration contract
Require
reasonin the cancellation dialog and submit exactly{ reason }.Use
orders_countandreleased_orders_countfrom session lists.Continue opening the existing session deliveries endpoint; render
session_membership_status=releasedrows as history and disable selection/removal actions.Display
session.cancellation.cancelled_at,.reason, and.cancelled_by. No new navigation action is returned.
Path parameters
| Parameter | Type | Description |
|---|---|---|
sessionIdRequired | string | Example: |
Headers
| Parameter | Type | Description |
|---|---|---|
Authorization | string | Example: |
Body parameters
| Parameter | Type | Description |
|---|---|---|
reason | string | — |
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/route-sessions/${SESSIONID}/cancel" \2 --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3 --header "Content-Type: application/json" \4 --data-raw '{5 "reason": "Driver unavailable"6}'Response examples
1{2 "success": true,3 "message": "Route session cancelled successfully",4 "data": {5 "id": "session-uuid",6 "status": "cancelled",7 "cancelled_at": "2026-08-03T10:00:00Z",8 "orders_count": 4,9 "released_orders_count": 4,10 "cancellation": {11 "cancelled_at": "2026-08-03T10:00:00Z",12 "reason": "Driver unavailable",13 "cancelled_by": {14 "id": "user-uuid",15 "full_name": "Admin Name"16 }17 }18 }19}