Skip to main content

Driver Bulk Pick

2 API calls in this section.

Driver-only continuous shipment-label validation and one-confirm pickup. The API preserves one result per submitted barcode, resolves staged parent labels to the active operational child, and resolves an original label with no active forward leg directly to its active linked return. It never exposes the failed forward delivery as a candidate or exposes the dispatcher generic bulk endpoint to the driver app. An outbound row whose parent has an open exception is rejected with stable reason parent_exception_open; the driver must not pick it until dispatch resolves the parent exception. After payment processing, successful Execute rows are published as one bounded notification operation (maximum 250 entities per queue job). Customer and driver notifications remain per applicable delivery; dispatchers/admins receive one summary per operation. Notification delivery is asynchronous, does not alter the Execute response, and is never the source of pickup truth.

Validate Driver Bulk Pick​

Delivery & Last-Mile / Driver Bulk Pick
POST/delivery/driver/bulk-pick/validate

Driver-only, read-only validation for 1–200 shipment labels. Driver identity is resolved from the authenticated user; driver_id is never accepted. Labels are normalized using the existing DEL<delivery_code> convention while input order and duplicate occurrences are preserved. A staged parent label resolves to its first active non-terminal child. When an original or P&D label has no active forward leg and its persisted return_delivery_id identifies an active linked return, the scanned label resolves directly to that return. The failed forward delivery is not returned as a candidate. All normal assignment, service, team, vehicle, zone, exception, Waybill, handoff, proof and capacity validation then runs against the return. If it is ineligible, the row returns that return order's actual rejection reason.

Self-assign behavior

  • When tenant driver_config.driver_can_self_assign is true, an unassigned Pending internal delivery may be claimed after manual-assignment policy, team, primary vehicle, service-vehicle, zone and cumulative-capacity checks.

  • When false, only an active assignment owned by the authenticated driver is eligible.

  • Another driver's assignment always returns assigned_to_another_driver. Pending acceptance returns assignment_pending_acceptance.

Pickup behavior

  • Eligible existing assignment statuses are assigned, accepted, and en_route_to_pickup. An order already at picked_up is rejected with already_picked_up and cannot be scanned for pickup again.

  • Open exceptions, non-internal carriers, active Waybills (drafted, sealed, assigned, picked_up, in_transit, awaiting_receipt), missing required handoff, and non-pickable states are rejected per row.

  • A pending relational P&D handoff is valid preparation: the execute call completes it atomically with stock exit and custody transfer. It must not be completed through the warehouse handoff API first.

  • V1 does not collect pickup proof. When the service policy requires pickup photo or signature, the row returns pickup_proof_required and must use the existing single-order flow.

  • Capacity is allocated deterministically in scan order only to new self-assignments; already assigned orders consume no new units.

  • If the original and direct return labels are both submitted, both resolve to the same delivery ID. The first occurrence is considered and the later occurrence returns duplicate_delivery, so the return is never counted or picked twice.

Important row reasons include unknown_barcode, duplicate_scan, duplicate_delivery, already_picked_up, delivery_not_ready_for_pickup, delivery_has_open_exception, delivery_in_active_waybill, assigned_to_another_driver, assignment_pending_acceptance, driver_self_assign_disabled, insufficient_vehicle_capacity, pickup_proof_required, and handoff_required_from_warehouse. Every message is localized; branch on reason, not message text.

Frontend integration contract

keep sending the physically scanned labels in the full accumulated barcodes array after each new local scan. For a linked-return resolution, keep results[].barcode as the scanned original label and use results[].resolved_delivery_id plus results[].delivery as authoritative; delivery.id, delivery.delivery_code, delivery.barcode, and delivery.delivery_flow describe the return. Merge rows by normalized scanned barcode and array position so duplicates remain visible. Use counts.scanned, .valid, .rejected and capacity. Enable review only when at least one row has valid=true. HTTP 200 may contain zero valid rows; 400 is malformed input, 403 is role/driver-state failure, and 500 is a global failure.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
barcodesarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/driver/bulk-pick/validate" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "barcodes": [6    "DEL1865",7    "DEL1866"8  ]9}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Bulk pickup labels validated",4  "results": [5    {6      "barcode": "DEL2863",7      "valid": true,8      "reason": "bulk_pick_valid_scan",9      "message": "Return order 2865 is ready for bulk pickup",10      "resolved_delivery_id": "RETURN-2865-ID",11      "delivery": {12        "id": "RETURN-2865-ID",13        "delivery_code": "2865",14        "barcode": "DEL2865",15        "status": "pending",16        "delivery_flow": "operational_return",17        "unit_size": 1,18        "pickup_summary": "Jaipur Warehouse",19        "dropoff_summary": "Return destination"20      }21    },22    {23      "barcode": "DEL1866",24      "valid": false,25      "reason": "assigned_to_another_driver",26      "message": "This order is assigned to another driver"27    }28  ],29  "counts": {30    "scanned": 2,31    "valid": 1,32    "rejected": 133  },34  "capacity": {35    "requested_new_units": 1,36    "accepted_new_units": 1,37    "available_units": 20,38    "exceeded": false39  }40}

Execute Driver Bulk Pick​

Delivery & Last-Mile / Driver Bulk Pick
POST/delivery/driver/bulk-pick/execute

Revalidates every physical barcode, including linked-return resolution, then sends eligible resolved deliveries through one service-role-only atomic database operation. An original label with no active forward leg is resolved again to its active linked return at execution time; only that return delivery ID is sent to the atomic pickup owner. If both original and return labels were submitted, the duplicate resolved ID is never executed twice. The RPC locks assignments, deliveries and the driver in stable order; rechecks ownership, status, exceptions, active Waybills, handoff and capacity; creates allowed self-assignments with one bulk_assignment_id; records the normal assigned/start/picked-up events and timestamps; and returns ordered partial results. A business conflict affects only that row.

For lifecycle-managed P&D and operational-return parcels, Pickup is the warehouse custody boundary. The same transaction requires the completed picking order, current assignment and current pending handoff; rechecks delivery and warehouse-item exceptions; writes one pick inventory movement; clears warehouse quantity/reservation; marks item custody in_transit; changes the picking order to handed_off; completes the handoff; and marks the delivery/assignment picked_up. Waybill and ordinary delivery pickup retain their existing behavior.

For successful database rows, the Edge Function uses the existing idempotent sender-cash pickup accounting and delivery-status notification services. Payment work runs with bounded concurrency. If an order reaches picked_up before or during execution, that row fails with already_picked_up; pickup transitions and side effects are not repeated.

Response rules

  • HTTP 200 represents completion of the batch request and may contain mixed success/failure rows.

  • counts.picked contains newly completed pickups; counts.failed contains rejected validation, execution conflicts, already-picked orders, or side-effect failures.

  • bulk_assignment_id is present only when at least one new self-assignment was created.

  • A state race returns row reason bulk_pick_state_changed; missing warehouse preparation returns delivery_not_ready_for_pickup; a missing current handoff returns handoff_required_from_warehouse; and other rows continue.

Frontend integration contract

submit only the currently valid physical barcodes once and disable Confirm while this request is pending. Do not replace an original scanned barcode with the return barcode before Execute; the backend resolves it again. Display each returned row using localized message and branch on stable reason. Keep failed rows available for review. Treat already_picked_up as a terminal rejection and do not offer it for pickup retry. After the response, navigate through the existing Orders flow; no later delivery API or status behavior changes.

Event actor contract

assignment and delivery status events created by this driver action use the validated drivers.id as actor_id. The backend retains the authenticated user ID for authorization and non-event ownership fields. Clients send barcodes only, not an actor ID.

Headers

ParameterTypeDescription
Authorizationstring

Example: Bearer {{access_token}}

Content-Typestring

Example: application/json

Body parameters

ParameterTypeDescription
barcodesarray<string>—
Staging
Request
curl
1curl --globoff --request POST "https://staging-api.ondi.io/functions/v1/delivery/driver/bulk-pick/execute" \2  --header "Authorization: Bearer ${ONDI_ACCESS_TOKEN}" \3  --header "Content-Type: application/json" \4  --data-raw '{5  "barcodes": [6    "DEL1865",7    "DEL1866"8  ]9}'

Response examples

200OK
Response body
json
1{2  "success": true,3  "message": "Bulk pickup completed",4  "results": [5    {6      "barcode": "DEL1865",7      "success": true,8      "reason": "bulk_pick_completed",9      "message": "Order picked up successfully",10      "delivery": {11        "id": "{{delivery_id}}",12        "delivery_code": "1865",13        "status": "picked_up",14        "assignment_id": "{{assignment_id}}"15      }16    },17    {18      "barcode": "DEL1866",19      "success": false,20      "reason": "already_picked_up",21      "message": "This order has already been picked up"22    }23  ],24  "counts": {25    "picked": 1,26    "failed": 127  },28  "bulk_assignment_id": "{{bulk_assignment_id}}"29}